View a markdown version of this page

Evitar problemas de desempenho com REPLICA IDENTITY FULL no RDS para PostgreSQL - Amazon Relational Database Service

Evitar problemas de desempenho com REPLICA IDENTITY FULL no RDS para PostgreSQL

A replicação lógica do PostgreSQL exige que cada tabela publicada tenha uma identidade de réplica para que o assinante possa localizar a linha correta a ser atualizada ou excluída. Por padrão, a chave primária serve como identidade de réplica. Quando uma tabela não tem chave primária ou índice exclusivo adequado, é possível definir a identidade da réplica como FULL, o que faz com que o PostgreSQL use a linha inteira como chave.

Embora a configuração REPLICA IDENTITY FULL resolva o problema imediato de replicar tabelas sem chaves primárias, ela pode causar sérios problemas de desempenho tanto para o publicador quanto para o assinante. Entender esses impactos é importante para qualquer pessoa que use a replicação lógica com o RDS para PostgreSQL, bem como recursos que dependem da replicação lógica interna, como implantações azuis/verdes.

Por que a configuração REPLICA IDENTITY FULL causa problemas

Aumento do volume de WAL no publicador

A configuração REPLICA IDENTITY controla quais informações o PostgreSQL grava no log de gravação antecipada (WAL) para identificar linhas atualizadas ou excluídas. Com a identidade de réplica padrão (chave primária), somente as colunas de chave são registradas em log como a identidade de linha antiga. Com FULL, o PostgreSQL registra os valores antigos de cada coluna para cada UPDATE e DELETE. Isso tem várias consequências:

  • O tamanho do WAL aumenta significativamente. Para atualizações, o tamanho de cada registro do WAL praticamente dobra porque os valores antigos e novos de cada coluna são registrados. Se a tabela contiver grandes valores armazenados por meio de TOAST, o aumento poderá ser bem maior porque os valores processados por TOAST devem ser buscados e gravados no WAL, mesmo que não tenham sido modificados pela atualização.

  • Maior uso de E/S e CPU no publicador. As gravações adicionais do WAL consomem maior largura de banda de E/S de disco e ciclos de CPU, especialmente para workloads com alto volume de gravação.

  • Mais dados enviados aos assinantes. O publicador precisa transmitir registros de WAL maiores pela rede para cada assinante, aumentando o consumo de largura de banda.

Pesquisas lentas de linha no assinante

Quando o assinante recebe um registro de log UPDATE ou DELETE, ele precisa encontrar a linha correspondente na respectiva cópia local da tabela. Com REPLICA IDENTITY FULL, o assinante procura uma linha que corresponda a todos os valores de coluna da imagem de linha antiga.

A forma como o PostgreSQL realiza essa pesquisa difere de acordo com a versão principal do PostgreSQL:

  • Antes do PostgreSQL 16: se a tabela não tiver chave primária e nenhum índice de identidade de réplica explicitamente configurado, o assinante executará uma verificação sequencial em toda a tabela para cada operação UPDATE ou DELETE. Em tabelas grandes, isso reduz extremamente o desempenho da aplicação.

  • PostgreSQL 16 e posterior: o assinante pode usar um índice btree ou de hash para pesquisas de linha, mesmo que esse índice não esteja explicitamente definido como a identidade de réplica. No entanto, o assinante não avalia qual índice é mais eficiente. A partir da versão 16, o PostgreSQL seleciona o primeiro índice adequado encontrado, e o usuário não tem controle sobre essa escolha. Se o índice selecionado tiver baixa seletividade (por exemplo, um índice em uma coluna booliana ou de status), a pesquisa de linha poderá ser quase tão lenta quanto uma verificação sequencial. Por esse motivo, contar com a seleção implícita do índice com REPLICA IDENTITY FULL não é confiável e deve ser considerado uma alternativa, não uma configuração recomendada.

Como REPLICA IDENTITY FULL causa atraso na replicação

Ambos os problemas descritos acima (WAL maior no publicador e pesquisas de linha mais lentas no assinante) se unem para causar atraso na replicação.

Por padrão, a replicação lógica do PostgreSQL usa um único processo de aplicação de operador por assinatura para receber alterações do publicador e aplicá-las às tabelas do assinante. Os processos do operador de aplicação são alterados em série, uma linha por vez, na ordem de confirmação. Isso significa que o throughput do assinante é limitado pela rapidez com que ele consegue aplicar cada alteração.

Quando REPLICA IDENTITY FULL está definida em uma tabela sem um índice apropriado, todas as operações UPDATE e DELETE requerem uma verificação sequencial da tabela completa para encontrar a linha correspondente. Se a tabela tiver milhões de linhas, cada uma dessas operações poderá levar segundos ou mais. O resultado é um problema em cascata:

  1. O publicador gera alterações mais depressa do que o assinante consegue aplicá-las. A workload de gravação do publicador continua na velocidade normal, mas o operador de aplicação do assinante fica congestionado devido a verificações sequenciais ou índices pouco seletivos para cada pesquisa de linha.

  2. O WAL se acumula no publicador e pode esgotar o armazenamento. O PostgreSQL não consegue recuperar segmentos do WAL enquanto o assinante não confirmar que os aplicou. Quanto mais o assinante se atrasa, mais o publicador acumula o WAL em disco. No RDS para PostgreSQL, isso parece aumentar OldestReplicationSlotLag no CloudWatch. Em casos graves, isso pode consumir todo o armazenamento disponível e fazer com que o publicador pare de aceitar gravações.

  3. O atraso é autorreforçador. À medida que o assinante se atrasa, a tabela do assinante aumenta com as inserções replicadas, tornando cada verificação sequencial ainda mais lenta. Sem intervenção, o atraso aumenta indefinidamente.

Esse problema é especialmente grave para tabelas que recebem operações UPDATE ou DELETE frequentes. As operações INSERT não são afetadas porque não exigem pesquisa de linha no assinante.

nota

A partir do PostgreSQL 16, o operador de aplicação pode usar a aplicação paralela para grandes transações de streaming, o que pode contribuir para o throughput. No entanto, o gargalo fundamental de pesquisa de linha para REPLICA IDENTITY FULL sem índices permanece, porque cada linha ainda assim requer uma verificação para ser localizada.

Impacto nas implantações azuis/verdes

No Amazon RDS, as implantações azuis/verdes usam a replicação lógica internamente para manter o ambiente verde sincronizado com o azul, configurando uma única assinatura por banco de dados. O processo de aplicação da replicação lógica no ambiente verde é de thread único. Um único processo de operador de aplicação recebe todas as alterações do ambiente azul e as aplica uma de cada vez, na ordem de confirmação. Não há aplicação paralela no caminho de replicação azul/verde.

Esse design de thread único significa que a capacidade do ambiente verde de acompanhar o ambiente azul depende inteiramente da rapidez com que um operador de aplicação consegue processar cada alteração. Quando as tabelas usam REPLICA IDENTITY FULL sem uma chave primária ou um índice adequado, o impacto no operador de aplicação depende da versão do PostgreSQL. Nas versões anteriores à 16, todas as operações UPDATE e DELETE nessas tabelas forçam o operador de aplicação a realizar uma verificação sequencial da tabela completa para encontrar a linha correspondente. Nas versões 16 e posteriores, o PostgreSQL usará um índice adequado, se houver um disponível, mas, se não houver nenhum índice qualificado, o operador de aplicação continuará a recorrer a uma verificação sequencial. Enquanto o operador de aplicação busca uma linha em uma tabela grande, todas as outras alterações pendentes em todas as tabelas entram na fila e ficam aguardando.

As consequências das implantações azuis/verdes são significativas:

  • O atraso na replicação aumenta continuamente. Se o ambiente azul gerar tráfego de gravação mais depressa do que um único operador de aplicação consegue processar, o ambiente verde ficará cada vez mais atrasado. Como o operador de aplicação segue um processo de thread único, não há como paralelizar a recuperação.

  • A transição pode ser obstruída. Uma transição azul/verde exige que o ambiente verde esteja totalmente sincronizado com o ambiente azul. Se o atraso na replicação for muito grande, a transição não poderá ser concluída no tempo-limite.

  • Talvez o ambiente verde nunca se recupere. Para workloads com alto volume de gravação que usam REPLICA IDENTITY FULL e nenhum índice, a taxa de aplicação pode ser tão lenta que o ambiente verde fica permanentemente atrasado, impossibilitando a transição sem primeiro resolver a configuração da identidade de réplica.

  • O WAL se acumula no ambiente azul. Enquanto o ambiente verde está atrasado, o ambiente azul retém os segmentos do WAL para o slot de replicação. Isso aumenta o uso do armazenamento no ambiente azul (produção) e pode afetar o desempenho da produção.

Para evitar esses problemas, garanta que todas as tabelas tenham uma chave primária ou um índice exclusivo adequado configurado explicitamente como a identidade de réplica usando ALTER TABLE ... REPLICA IDENTITY USING INDEX antes de criar uma implantação azul/verde. Não recorra à configuração REPLICA IDENTITY FULL com seleção implícita de índice no PostgreSQL 16 e posterior, pois o assinante pode escolher um índice pouco seletivo ou recorrer a verificações sequenciais. Teste a implantação com uma workload de gravação representativa para confirmar se o ambiente verde consegue se ajustar.

Para ter mais informações sobre as limitações das implantações azuis/verdes, consulte Limitações e considerações relativas às implantações azul/verde do Amazon RDS. Para ver as práticas recomendadas, consulte Práticas recomendadas do RDS para PostgreSQL para implantações azuis/verdes.

Como identificar tabelas que usam REPLICA IDENTITY FULL

Execute a seguinte consulta para encontrar todas as tabelas com REPLICA IDENTITY FULL:

SELECT n.nspname AS schema, c.relname AS table_name, c.relreplident FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'r' AND c.relreplident = 'f' AND n.nspname NOT IN ('pg_catalog', 'information_schema') ORDER BY n.nspname, c.relname;

Os valores da coluna relreplident são:

  • d: default (chave primária).

  • n: nothing.

  • f: full (linha inteira).

  • i: um índice específico.

Soluções alternativas e práticas recomendadas

Adicione uma chave primária sempre que possível

A solução mais eficaz é adicionar uma chave primária às tabelas que não têm uma. Quando existe uma chave primária, o PostgreSQL a utiliza como a identidade de réplica padrão, o que oferece pesquisas de linha eficientes no assinante e minimiza a sobrecarga do WAL no publicador.

ALTER TABLE my_table ADD COLUMN id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY;
Importante

Essa instrução adquire um bloqueio ACCESS EXCLUSIVE e reescreve a tabela inteira, porque a expressão de valor padrão usa nextval(), que é volátil. Todas as leituras e gravações na tabela são bloqueadas durante a regravação. Em tabelas grandes, isso pode provocar um tempo de inatividade significativo. Planeje essa alteração durante uma janela de manutenção ou considere abordagens alternativas, como criar primeiro a coluna como anulável, depois preencher e adicionar a restrição em etapas separadas.

Se a adição de uma chave primária não for viável devido a restrições da aplicação, considere a possibilidade de adicionar um índice exclusivo em um conjunto de colunas NOT NULL e defini-lo como a identidade de réplica:

CREATE UNIQUE INDEX my_table_replica_idx ON my_table (col1, col2); ALTER TABLE my_table REPLICA IDENTITY USING INDEX my_table_replica_idx;
nota

Para evitar o bloqueio de gravações durante a criação do índice, use a cláusula CONCURRENTLY: CREATE UNIQUE INDEX CONCURRENTLY my_table_replica_idx ON my_table (col1, col2);

nota

O índice usado para a identidade de réplica deve ser exclusivo, não deve ser parcial, não deve ser adiável e deve incluir somente colunas com restrições NOT NULL.

Não recorra à seleção implícita de índice (PostgreSQL 16 e posterior)

A partir do PostgreSQL 16, o operador de aplicação do assinante pode usar índices btree ou de hash para pesquisas de linha quando a identidade de réplica está definida como FULL, mesmo que esses índices não estejam explicitamente configurados como a identidade de réplica. Embora isso evite verificações sequenciais em alguns casos, recorrer a esse comportamento implícito é um antipadrão pelos seguintes motivos:

  • Não é possível controlar qual índice é escolhido. O PostgreSQL seleciona o primeiro índice qualificado que ele encontra na ordem do catálogo, não o mais seletivo ou eficiente. Se a tabela tiver vários índices qualificados, o escolhido pode ter pouca seletividade, levando a um desempenho de pesquisa ruim.

  • O comportamento é frágil. Adicionar, eliminar ou recriar índices pode alterar o índice que o operador de aplicação usa e, possivelmente, provocar regressões de desempenho inesperadas na replicação.

  • Isso mascara o problema subjacente. Tabelas sem chave primária ou identidade de réplica explícita são inerentemente arriscadas para a replicação lógica. Recorrer à seleção implícita do índice protela o problema em vez de resolvê-lo.

Em vez disso, configure explicitamente a identidade de réplica para cada tabela replicada:

  • Melhor opção: adicionar uma chave primária. Essa é a identidade de réplica mais confiável e eficiente.

  • Alternativa: use ALTER TABLE ... REPLICA IDENTITY USING INDEX para designar um índice específico exclusivo, não parcial, não adiável e apenas com colunas NOT NULL. Isso oferece controle explícito sobre quais colunas são usadas para identificação de linhas.

Reserve REPLICA IDENTITY FULL somente para tabelas em que nenhuma das opções seja viável e entenda que o desempenho depende de fatores que estão fora de seu controle direto.

Monitorar o atraso na replicação

Ao usar REPLICA IDENTITY FULL, monitore atentamente o atraso na replicação para detectar lentidões de aplicação no assinante antes que elas se tornem graves.

No publicador, verifique a defasagem entre a posição atual do WAL e o que o assinante confirmou:

SELECT slot_name, confirmed_flush_lsn, pg_current_wal_lsn(), (pg_current_wal_lsn() - confirmed_flush_lsn) AS lag_bytes FROM pg_replication_slots WHERE slot_type = 'logical';

Um valor lag_bytes que não para de aumentar indica que o assinante está se atrasando. A visualização pg_stat_replication_slots apresenta estatísticas adicionais sobre o uso de cada slot de replicação.

No assinante, a visualização pg_stat_subscription mostra o estado de cada operador de aplicação, inclusive a última localização do WAL recebida e relatada:

SELECT subname, received_lsn, latest_end_lsn, last_msg_send_time, last_msg_receipt_time FROM pg_stat_subscription;
nota

No PostgreSQL 16 e posterior, também é possível selecionar worker_type para fazer a distinção entre o operador de aplicação principal e os operadores de aplicação paralelos.

Uma grande defasagem entre received_lsn e latest_end_lsn, ou carimbos de data/hora obsoletos em last_msg_send_time, pode indicar que o operador de aplicação está tendo dificuldades para acompanhar. A visualização pg_stat_subscription_stats também rastreia erros e conflitos de aplicação que podem contribuir para o atraso.

Para o RDS para PostgreSQL, também é possível monitorar a métrica OldestReplicationSlotLag do CloudWatch, que rastreia o atraso em bytes do slot de replicação mais atrasado. Um valor crescente é um aviso antecipado de atraso na replicação.

Verificar quais tabelas podem estar usando um índice abaixo do ideal durante a aplicação

No assinante, é possível identificar tabelas nas quais o operador de aplicação está realizando leituras excessivas de pilha, o que pode indicar que a tabela não tem um índice eficiente para pesquisas de linha durante a aplicação. Execute a seguinte consulta no assinante:

SELECT relname, heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit, heap_blks_read + heap_blks_hit AS total_heap_access FROM pg_statio_user_tables WHERE heap_blks_read > 0 ORDER BY heap_blks_read DESC LIMIT 10;

Uma tabela com um valor heap_blks_read alto em relação a idx_blks_read pode indicar que o operador de aplicação não está usando um índice eficiente para localizar linhas para as operações UPDATE e DELETE. Esse é um motivo comum de atraso na replicação quando se usa REPLICA IDENTITY FULL.

nota

Essa consulta exige que o parâmetro track_counts seja habilitado no assinante. Por padrão, esse parâmetro está ativado.

Avalie se a configuração REPLICA IDENTITY FULL é necessária

Antes de configurar REPLICA IDENTITY FULL, considere se você realmente precisa dessa configuração. Alguns dos motivos comuns para usá-la são:

  • A tabela não tem chave primária nem índice exclusivo.

  • Você precisa da imagem anterior completa das linhas para consumidores de captura de dados de alteração (CDC).

  • Você precisa que os valores da coluna TOASTed sejam incluídos nos eventos de replicação para atualizações que não modificam essas colunas.

Se o único motivo for a falta de uma chave primária, adicionar uma é quase sempre a melhor solução. Se você precisar de imagens anteriores completas para a CDC, considere se seu consumidor de CDC consegue reconstruir linhas completas mantendo o estado externamente, o que evita a sobrecarga do WAL e do assinante da configuração REPLICA IDENTITY FULL.

Resumo das recomendações

Cenário Recomendação
A tabela tem uma chave primária Use a identidade de réplica padrão (nenhuma ação é necessária).
A tabela tem um índice NOT NULL exclusivo Defina esse índice como a identidade de réplica com ALTER TABLE ... REPLICA IDENTITY USING INDEX.
A tabela não tem uma chave adequada (PostgreSQL 16 e posterior) Adicione uma chave primária ou um índice exclusivo. O uso de REPLICA IDENTITY FULL com seleção implícita de índice não é confiável e deve ser o último recurso.
A tabela não tem uma chave adequada (antes do PostgreSQL 16) Adicione uma chave primária ou índice exclusivo; evite REPLICA IDENTITY FULL se possível.
Workload com alto volume de gravação e colunas grandes/TOASTed Evite REPLICA IDENTITY FULL devido à amplificação do volume do WAL.