

# Evitar problemas de desempenho com REPLICA IDENTITY FULL no Aurora PostgreSQL
<a name="PostgreSQL.ReplicaIdentityFull"></a>

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 Aurora 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
<a name="PostgreSQL.ReplicaIdentityFull.WhyProblems"></a>

### Aumento do volume de WAL no publicador
<a name="PostgreSQL.ReplicaIdentityFull.WALVolume"></a>

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](https://www.postgresql.org/docs/current/storage-toast.html), 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
<a name="PostgreSQL.ReplicaIdentityFull.SlowLookups"></a>

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](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=89e46da5e), o PostgreSQL seleciona o [primeiro índice adequado](https://www.postgresql.org/docs/18/logical-replication-publication.html#LOGICAL-REPLICATION-PUBLICATION-REPLICA-IDENTITY) 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
<a name="PostgreSQL.ReplicaIdentityFull.ReplicationLag"></a>

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.

1. **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 Aurora 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.

1. **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
<a name="PostgreSQL.ReplicaIdentityFull.BlueGreen"></a>

No Amazon Aurora, 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](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=89e46da5e), 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 Aurora](blue-green-deployments-considerations.md). Para ver as práticas recomendadas, consulte [Práticas recomendadas do Aurora PostgreSQL para implantações azuis/verdes](blue-green-deployments-best-practices.md#blue-green-deployments-best-practices-postgres).

## Como identificar tabelas que usam REPLICA IDENTITY FULL
<a name="PostgreSQL.ReplicaIdentityFull.Identify"></a>

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
<a name="PostgreSQL.ReplicaIdentityFull.Workarounds"></a>

### Adicione uma chave primária sempre que possível
<a name="PostgreSQL.ReplicaIdentityFull.AddPrimaryKey"></a>

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 [https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY](https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-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 name="PostgreSQL.ReplicaIdentityFull.SubscriberIndexes"></a>

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
<a name="PostgreSQL.ReplicaIdentityFull.MonitorLag"></a>

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 Aurora 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. Para obter mais informações, consulte [Monitorar o cache de gravação simultânea e os slots lógicos para a replicação lógica do Aurora PostgreSQL](AuroraPostgreSQL.Replication.Logical-monitoring.md).

**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 [https://www.postgresql.org/docs/current/runtime-config-statistics.html#GUC-TRACK-COUNTS](https://www.postgresql.org/docs/current/runtime-config-statistics.html#GUC-TRACK-COUNTS) seja habilitado no assinante. Por padrão, esse parâmetro está ativado.

### Avalie se a configuração REPLICA IDENTITY FULL é necessária
<a name="PostgreSQL.ReplicaIdentityFull.Evaluate"></a>

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
<a name="PostgreSQL.ReplicaIdentityFull.Summary"></a>


| 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. | 