Noções básicas sobre conflitos ativos-ativos - Amazon Relational Database Service

Noções básicas sobre conflitos ativos-ativos

Quando você usa pgactive no modo ativo-ativo, gravar nas mesmas tabelas por meio de vários nós pode criar conflitos de dados. Embora alguns sistemas de clustering usem bloqueios distribuídos para impedir o acesso simultâneo, a pgactive adota uma abordagem otimista que é mais adequada para aplicações distribuídas geograficamente.

Alguns sistemas de clustering de banco de dados impedem o acesso simultâneo aos dados por meio de bloqueios distribuídos. Embora essa abordagem funcione quando os servidores estão próximos, não é possível usá-la para aplicações distribuídas geograficamente porque, para oferecer um bom desempenho, é necessária uma latência extremamente baixa. Em vez de usar bloqueios distribuídos (uma abordagem pessimista), a extensão pgactive usa uma abordagem otimista. Isso significa que ela:

  • Ajuda a evitar conflitos quando possível.

  • Possibilita que determinados tipos de conflito ocorram.

  • Oferece resolução quando ocorrem conflitos.

Essa abordagem oferece maior flexibilidade ao criar aplicações distribuídas.

Como os conflitos acontecem

Os conflitos entre nós surgem de sequências de eventos que não poderiam acontecer se todas as transações envolvidas ocorressem simultaneamente no mesmo nó. Como os nós só trocam alterações após a confirmação das transações, cada transação é válida individualmente no nó em que foi confirmada, mas não seria válida se executada em outro nó que tivesse feito outro trabalho nesse meio tempo. Como o apply da pgactive basicamente reproduz a transação nos outros nós, a operação de repetição pode falhar se houver um conflito entre uma transação que está sendo aplicada e uma transação que foi confirmada no nó receptor.

O motivo pelo qual a maioria dos conflitos não pode acontecer quando todas as transações são executadas em um único nó é que o PostgreSQL tem mecanismos de comunicação entre transações para evitá-los, como:

  • Índices UNIQUE

  • SEQUENCEs

  • Bloqueio de linha e relação

  • Rastreamento de dependências SERIALIZÁVEL

Todos esses mecanismos são formas de comunicação entre transações para evitar problemas indesejáveis de simultaneidade.

A pgactive alcança baixa latência e lida bem com partições de rede porque não usa um gerenciador de transações distribuídas ou gerenciador de bloqueios. Mas isso significa que as transações em nós diferentes são executadas de maneira totalmente isolada umas das outras. Embora o isolamento normalmente melhore a consistência do banco de dados, nesse caso, você precisa reduzir o isolamento para evitar conflitos.

Tipos de conflito

Os conflitos que podem ocorrer incluem:

Conflitos de CHAVE PRIMÁRIA ou UNIQUE

Os conflitos de linha ocorrem quando várias operações tentam modificar a mesma chave de linha de uma maneira não possíveis em um único nó. Esses conflitos representam o tipo mais comum de conflito de dados.

A pgactive resolve conflitos detectados por meio da abordagem de última atualização prevalece ou de seu manipulador de conflitos personalizado.

Os conflitos de linha incluem:

  • INSERT versus INSERT

  • INSERT versus UPDATE

  • UPDATE versus DELETE

  • INSERT versus DELETE

  • DELETE versus DELETE

  • INSERT versus DELETE

Conflitos INSERT/INSERT

Esse conflito mais comum ocorre quando INSERTs em dois nós diferentes criam uma tupla com os mesmos valores de CHAVE PRIMÁRIA (ou valores de restrição UNIQUE idênticos quando não existe CHAVE PRIMÁRIA).

O pgactivelink resolve conflitos de INSERT usando o carimbo de data/hora do host de origem para manter a tupla mais recente. Você pode substituir esse comportamento padrão com seu manipulador de conflitos personalizado. Embora esse processo não exija nenhuma ação especial do administrador, esteja ciente de que o pgactivelink descarta uma das operações INSERT em todos os nós. Nenhuma mesclagem automática de dados ocorre, a menos que seu manipulador personalizado a implemente.

O pgactivelink só pode resolver conflitos envolvendo uma única violação de restrição. Se um INSERT violar várias restrições UNIQUE, você deverá implementar estratégias adicionais de resolução de conflitos.

INSERTs que violam várias restrições UNIQUE

Um conflito INSERT/INSERT pode violar várias restrições UNIQUE, incluindo a CHAVE PRIMÁRIA. O pgactivelink só pode lidar com conflitos que envolvam uma única restrição UNIQUE. Quando os conflitos violarem várias restrições UNIQUE, o operador apply falhará e retornará o seguinte erro:

multiple unique constraints violated by remotely INSERTed tuple.

Em versões mais antigas, essa situação gerava um erro de “conflito de exclusividade divergente”.

Para resolver esses conflitos, você deve executar uma ação manual. Use DELETE para excluir as tuplas locais conflitantes ou UPDATE para atualizá-las e remover os conflitos com a nova tupla remota. Esteja ciente de que talvez você precise resolver várias tuplas conflitantes. No momento, o pgactivelink não oferece nenhuma funcionalidade integrada para ignorar, descartar ou mesclar tuplas que violem várias restrições únicas.

nota

Para ter mais informações, consulte UPDATEs que violam várias restrições UNIQUE.

Conflitos UPDATE/UPDATE

Esse conflito ocorre quando dois nós modificam simultaneamente a mesma tupla sem alterar a CHAVE PRIMÁRIA. O pgactivelink resolve esses conflitos usando a lógica de última atualização prevalece ou seu manipulador de conflitos personalizado, se definido. Uma CHAVE PRIMÁRIA é essencial para correspondência de tuplas e resolução de conflitos. Para tabelas sem CHAVE PRIMÁRIA, o pgactivelink rejeita as operações UPDATE com o seguinte erro:

Cannot run UPDATE or DELETE on table (tablename) because it does not have a primary key.

Conflitos UPDATE na CHAVE PRIMÁRIA

A pgactive tem limitações para lidar com atualizações de CHAVE PRIMÁRIA. Embora você possa realizar a operação UPDATE em uma CHAVE PRIMÁRIA, a pgactive não consegue resolver conflitos automaticamente usando a lógica de última atualização prevalece para essas operações. Você deve garantir que as atualizações da CHAVE PRIMÁRIA não entrem em conflito com os valores existentes. Se ocorrerem conflitos durante as atualizações da CHAVE PRIMÁRIA, eles se tornarão conflitos divergentes e exigirão intervenção manual. Para ter mais informações sobre como lidar com essas situações, consulte Conflitos divergentes.

UPDATEs que violam várias restrições UNIQUE

O pgactivelink não consegue aplicar a resolução de conflitos de última atualização prevalece quando um UPDATE recebido viola várias restrições UNIQUE ou valores de CHAVE PRIMÁRIA. Esse comportamento é semelhante às operações INSERT com várias violações de restrição. Essas situações criam conflitos divergentes que exigem intervenção manual. Para obter mais informações, consulte Conflitos divergentes.

Conflitos UPDATE/DELETE

Esses conflitos ocorrem quando um nó executa UPDATE em uma linha e outro nó executa simultaneamente DELETE nessa linha. Nesse caso, ocorre um conflito UPDATE/DELETE na reprodução. A solução é descartar qualquer UPDATE que chegue após um DELETE, a menos que seu manipulador de conflitos personalizado especifique o contrário.

O pgactivelink requer uma CHAVE PRIMÁRIA para combinar tuplas e resolver conflitos. Para tabelas sem CHAVE PRIMÁRIA, ele rejeita operações DELETE com o seguinte erro:

Cannot run UPDATE or DELETE on table (tablename) because it does not have a primary key.

nota

O pgactivelink não consegue distinguir entre os conflitos UPDATE/DELETE e INSERT/UPDATE. Em ambos os casos, um UPDATE afeta uma linha inexistente. Devido à replicação assíncrona e à falta de ordem de reprodução entre os nós, o pgactivelink não consegue determinar se o UPDATE é para uma nova linha (INSERT ainda não recebido) ou uma linha excluída. Em ambos os cenários, o pgactivelink descarta o UPDATE.

Conflitos INSERT/UPDATE

Esse conflito pode ocorrer em ambientes com vários nós. Isso acontece quando um nó insere uma linha, um segundo nó a atualiza e um terceiro nó recebe o UPDATE antes do INSERT original. Por padrão, o pgactivelink resolve esses conflitos descartando o UPDATE, a menos que seu acionador de conflito personalizado especifique o contrário. Esteja ciente de que esse método de resolução pode provocar inconsistências de dados entre os nós. Para ter mais informações sobre cenários semelhantes e o respectivo tratamento, consulte Conflitos UPDATE/DELETE.

Conflitos DELETE/DELETE

Esse conflito ocorre quando dois nós diferentes excluem simultaneamente a mesma tupla. O pgactivelink considera esses conflitos não prejudiciais porque as duas operações DELETE têm o mesmo resultado final. Nesse cenário, o pgactivelink ignora com segurança uma das operações DELETE sem afetar a consistência de dados.

Conflitos de restrição de chave estrangeira

As restrições de CHAVE ESTRANGEIRA podem causar conflitos ao aplicar transações remotas aos dados locais existentes. Esses conflitos geralmente ocorrem quando as transações são aplicadas em uma sequência diferente da ordem lógica nos nós de origem.

Por padrão, a pgactive aplica alterações com session_replication_role como replica, o que ignora as verificações de chave estrangeira durante a replicação. Em configurações ativas-ativas, isso pode provocar violações de chave estrangeira. A maioria das violações é temporária e resolvida quando a replicação é concluída. Mas pode haver chaves estrangeiras pendentes porque a pgactive não permite o bloqueio de linhas entre nós.

Esse comportamento é inerente aos sistemas ativos-ativos assíncronos com tolerância à partição. Por exemplo, o nó A pode inserir uma nova linha secundária enquanto o nó B exclui simultaneamente a linha principal. O sistema não consegue impedir esse tipo de modificação simultânea entre os nós.

Para minimizar conflitos de chave estrangeira, recomendamos o seguinte:

  • Limite as relações de chave estrangeira a entidades estreitamente relacionadas.

  • Modifique as entidades relacionadas por meio de um único nó quando possível.

  • Escolha entidades que raramente exijam modificação.

  • Implemente o controle de simultaneidade em nível de aplicação para modificações.

Conflitos de restrição de exclusão

O pgactivelink não permite restrições de exclusão e limita a criação de restrições.

nota

Se você converter um banco de dados independente existente em um banco de dados pgactivelink, remova manualmente todas as restrições de exclusão.

Em um sistema assíncrono distribuído, não é possível garantir que nenhum conjunto de linhas viole a restrição. Isso ocorre porque todas as transações em nós diferentes são totalmente isoladas. As restrições de exclusão podem gerar deadlocks de reprodução, impedindo que a reprodução progrida de um nó para outro devido a violações da restrição de exclusão.

Se você forçar o pgactivelink a criar uma restrição de exclusão ou se não remover as existentes ao converter um banco de dados independente em pgactivelink, é provável que a replicação seja interrompida. Para restaurar o progresso da replicação, remova ou altere as tuplas locais que estão em conflito com uma tupla remota de entrada para que a transação remota possa ser aplicada.

Conflitos de dados globais

Ao usar o pgactivelink, conflitos podem ocorrer quando os nós têm diferentes dados globais do sistema geral do PostgreSQL, como funções. Esses conflitos podem fazer com que as operações, principalmente de DDL, sejam bem-sucedidas e confirmadas em um nó, mas falhem ao serem aplicadas a outros nós.

Se um usuário existir em um nó e não em outro, podem ocorrer problemas de replicação:

  • O Node1 tem um usuário chamado fred, mas esse usuário não existe no Node2.

  • Quando fred cria uma tabela no Node1, a tabela é replicada com fred como proprietário.

  • Quando esse comando de DDL é aplicado ao Node2, ele falha porque o usuário fred não existe.

  • Essa falha gera um ERRO nos logs do PostgreSQL no Node2 e incrementa o contador pgactive.pgactive_stats.nr_rollbacks.

Resolução: crie o usuário fred no Node2. O usuário não precisa de permissões idênticas, mas deve existir em ambos os nós.

Se uma tabela existir em um nó e não em outro, as operações de modificação de dados vão falhar:

  • O Node1 tem uma tabela chamada foo que não existe no Node2.

  • Qualquer operação de DML na tabela foo no Node1 falhará quando replicada para o Node2.

Resolução: crie a tabela foo no Node2 com a mesma estrutura.

nota

No momento, o pgactivelink não replica comandos CREATE USER ou operações de DDL. A replicação de DDL está programada para uma versão futura.

Conflitos de bloqueio e interrupções decorrentes de deadlock

Como os processos apply da pgactive operam como sessões normais de usuário, eles seguem as regras padrão de bloqueio de linhas e tabelas. Isso pode fazer com que os processos apply do pgactivelink aguardem bloqueios mantidos por transações de usuário ou por outros processos apply.

Os seguintes tipos de bloqueio podem afetar os processos apply:

  • Bloqueio explícito em nível de tabela (LOCK TABLE…) por sessão de usuário

  • Bloqueio explícito em nível de linha (SELECT… FOR UPDATE/FOR SHARE) por sessão de usuário

  • Bloqueio de chaves estrangeiras

  • Bloqueio implícito devido operações de UPDATE, INSERT ou DELETE de linhas, seja da atividade local ou do operador apply de outros servidores

Deadlocks podem ocorrer entre:

  • Um processo apply do pgactivelink e uma transação do usuário

  • Dois processos apply

Quando ocorrem deadlocks, o detector de deadlock do PostgreSQL encerra uma das transações problemáticas. Se o processo do operador apply do pgactivelink for encerrado, ele fará uma nova tentativa automaticamente e em geral terá êxito.

nota
  • Esses problemas são temporários e normalmente não exigem intervenção do administrador. Se um processo apply for bloqueado por um longo período devido ao bloqueio de uma sessão de usuário ociosa, você poderá encerrar a sessão do usuário para retomar a replicação. Essa situação é semelhante a quando um usuário mantém um bloqueio longo que afeta a sessão de outro usuário.

  • Para identificar atrasos na reprodução relacionados ao bloqueio, habilite o recurso log_lock_waits no PostgreSQL.

Conflitos divergentes

Conflitos divergentes ocorrem quando dados que deveriam ser idênticos entre os nós diferem inesperadamente. Embora esses conflitos não devam acontecer, nem todos podem ser evitados de forma confiável na implementação atual.

nota

A modificação da CHAVE PRIMÁRIA de uma linha poderá causar conflitos divergentes se outro nó alterar a chave da mesma linha antes de todos os nós processarem a alteração. Evite alterar as chaves primárias ou restrinja as alterações a um nó designado. Para obter mais informações, consulte Conflitos UPDATE na CHAVE PRIMÁRIA .

Conflitos divergentes envolvendo dados de linha geralmente exigem intervenção do administrador. Para resolver esses conflitos, você deve ajustar manualmente os dados em um nó para corresponder a outro e, ao mesmo tempo, desabilitar temporariamente a replicação usando pgactive.pgactive_do_not_replicate. Esses conflitos não devem ocorrer quando você usa a pgactive conforme documentado e evita configurações ou funções marcadas como inseguras.

Como administrador, você deve resolver esses conflitos manualmente. Dependendo do tipo de conflito, você precisará usar opções avançadas, como pgactive.pgactive_do_not_replicate. Use essas opções com cuidado, pois o uso indevido pode piorar a situação. Devido à variedade de possíveis conflitos, não podemos fornecer instruções gerais de resolução.

Conflitos divergentes ocorrem quando dados que deveriam ser idênticos entre diferentes nós diferem inesperadamente. Embora esses conflitos não devam acontecer, nem todos podem ser evitados de forma confiável na implementação atual.

Evitar ou permitir conflitos

Na maioria dos casos, você pode usar o design de aplicação adequado para evitar conflitos ou tornar a aplicação tolerante a conflitos.

Os conflitos só ocorrem quando acontecem operações simultâneas em vários nós. Como evitar conflitos:

  • Grave em apenas um nó.

  • Grave em subconjuntos de banco de dados independentes em cada nó (por exemplo, atribua um esquema separado a cada nó).

Quanto a conflitos INSERT versus INSERT, use sequências globais para evitar conflitos por completo.

Se os conflitos não forem aceitáveis para seu caso de uso, considere implementar o bloqueio distribuído no nível da aplicação. Muitas vezes, a melhor abordagem é projetar a aplicação para funcionar com os mecanismos de resolução de conflitos da pgactive, em vez de tentar evitar todos os conflitos. Para obter mais informações, consulte Tipos de conflito.

Registro em log de conflitos

O pgactivelink registra incidentes de conflito na tabela pgactive.pgactive_conflict_history para ajudar você a diagnosticar e lidar com conflitos ativos-ativos. O registro em log de conflitos nessa tabela ocorre somente quando você define pgactive.log_conflicts_to_table como verdadeiro. A extensão pgactive também registra em log conflitos no arquivo de log do PostgreSQL quando log_min_messages é definido como LOG ou lower, independentemente da configuração pgactive.log_conflicts_to_table.

Use a tabela do histórico de conflitos para:

  • Medir a frequência com que a aplicação cria conflitos.

  • Identificar onde os conflitos ocorrem.

  • Melhorar a aplicação para reduzir as taxas de conflito.

  • Detectar casos em que as resoluções de conflito não produzem os resultados desejados.

  • Determinar onde você precisa de acionadores de conflito definidos pelo usuário ou alterações no design da aplicação.

Quanto a conflitos de linha, você pode, opcionalmente, registrar em log valores de linha. Isso é controlado pela configuração pgactive.log_conflicts_to_table. Observe que:

  • Essa opção é global para todo o banco de dados.

  • Não há controle por tabela sobre o registro em log de valores de linha.

  • Nenhum limite é aplicado a números de campo, elementos de matriz ou comprimentos de campo.

  • A habilitação desse recurso pode não ser aconselhável se você trabalha com linhas de vários megabytes que podem desencadear conflitos.

Como a tabela do histórico de conflitos contém dados de todas as tabelas do banco de dados (cada uma com esquemas possivelmente diferentes), os valores das linhas registradas em log são armazenados como campos JSON. O JSON é criado usando row_to_json, de forma semelhante a chamá-lo diretamente do SQL. O PostgreSQL não oferece uma função json_to_row, então você precisará de um código específico de tabela (em PL/pgSQL, PL/Python, PL/Perl etc.) para reconstruir uma tupla de tipo composto por meio do JSON registrado em log.

nota

O suporte a conflitos definidos pelo usuário está programado como um futuro recurso de extensão.