Alta disponibilidade e replicação no PostgreSQL: o que ainda dói
Streaming replication é sólida, mas failover automático não vem na caixa. Slots, synchronous_commit, split-brain, Patroni e o pesadelo da latência em ambiente híbrido — com SQL e configuração real.

Tem uma frase que eu ouço em toda reunião de arquitetura: "a replicação do Postgres é muito boa". É verdade. O que ninguém completa é a segunda parte: replicação não é alta disponibilidade. Replicação é cópia de dados. Alta disponibilidade é alguém decidir, sozinho e rápido, qual nó manda agora — e isso o PostgreSQL deliberadamente não faz por você.
Os três sabores de replicação (e quando cada um serve)
| Tipo | O que replica | Serve para | Limitação |
|---|---|---|---|
| Física (streaming) | Blocos de WAL, o cluster inteiro | HA, DR, réplicas de leitura | Mesma versão major, réplica read-only, tudo ou nada |
| Lógica | Linhas, por tabela ou publicação | Upgrade sem downtime, CDC, consolidação | Não replica DDL nem sequences por padrão |
| Backup contínuo (PITR) | WAL arquivado mais base backup | Recuperação a um ponto no tempo | RTO alto; não é failover |
Streaming replication: o mínimo que funciona
No primário, o essencial da configuração:
-- Verifique antes de mexer
SHOW wal_level; -- precisa ser 'replica' (ou 'logical')
SHOW max_wal_senders; -- pelo menos número de réplicas + 2
SHOW wal_keep_size;
-- Role dedicada para replicação (nunca use o superusuário da aplicação)
CREATE ROLE replicador WITH REPLICATION LOGIN PASSWORD 'troque-isto';
-- Slot de replicação: garante que o WAL não seja descartado
SELECT * FROM pg_create_physical_replication_slot('replica_sp1');
E a linha que separa quem dorme de quem não dorme:
-- Confirma commit só quando a réplica gravou o WAL
ALTER SYSTEM SET synchronous_commit = 'remote_write';
ALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (replica_sp1, replica_rj1)';
SELECT pg_reload_conf();
O preço do síncrono
Cada COMMIT passa a esperar a rede. Em datacenter único com 0,3 ms de RTT, ninguém percebe. Em híbrido on-premise para cloud com 18 ms, cada COMMIT ganha 18 ms — e a sua aplicação que faz 40 commits por request acabou de ganhar 720 ms. Aqui a escolha vira negócio: quantos milissegundos de latência valem zero byte de perda de dados?
Meu padrão: ANY 1 com duas réplicas locais síncronas e a réplica remota assíncrona para DR. Você protege contra perda de dados sem amarrar o COMMIT à WAN.
O perigo silencioso dos slots órfãos
Slot de replicação garante que o WAL espera a réplica. Se a réplica morre e ninguém remove o slot, o primário acumula WAL até encher o disco e parar. Já vi isso derrubar produção em feriado mais de uma vez. Monitore:
SELECT slot_name, active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_retido
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
A partir do PostgreSQL 13 existe rede de segurança: max_slot_wal_keep_size. Configure. Sempre.
Failover automático: Patroni e o problema do split-brain
Patroni é o padrão de mercado. Ele usa um DCS (etcd, Consul ou Kubernetes) para eleição por consenso, e só o nó que segura a chave de líder aceita escrita. O trecho que importa:
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576 # 1 MB - acima disso, nao promove
synchronous_mode: true
postgresql:
parameters:
synchronous_commit: remote_write
pg_hba:
- hostssl replication replicador 10.0.0.0/8 scram-sha-256
Três armadilhas que vejo direto:
- DCS com dois nós. Consenso precisa de quórum ímpar. Dois nós não é cluster, é empate.
- Sem fencing. Se o primário antigo volta e ainda tem VIP ou conexões apontando, você tem dois primários aceitando escrita. Isso é split-brain, e o estrago é irreversível.
- Aplicação com IP fixo. Use HAProxy ou PgBouncer com health check no endpoint do Patroni, ou o failover só troca o problema de lugar.
O teste que quase ninguém faz
Backup não testado não é backup. Failover não ensaiado não é HA. Meu ritual trimestral, em janela controlada:
- Matar o processo do primário à força (não shutdown limpo — a vida real não é limpa).
- Cronometrar até a aplicação voltar a escrever. Esse número é o seu RTO real.
- Conferir se houve transação perdida comparando o último LSN replicado.
- Reintegrar o nó antigo com
pg_rewinde medir quanto tempo levou.
pg_rewind --target-pgdata=/var/lib/postgresql/16/main \
--source-server="host=novo-primario user=replicador dbname=postgres" \
--progress
Réplica de leitura não é bala de prata
Mandar relatório para a réplica é ótimo, até a query de 40 minutos entrar em conflito com o replay do WAL. Aí você escolhe entre cancelar a query (max_standby_streaming_delay) ou atrasar a limpeza no primário (hot_standby_feedback = on, que devolve bloat — assunto do artigo sobre VACUUM e bloat).
Não existe configuração certa aqui, existe a escolha consciente. O erro é descobrir qual você fez durante o incidente.
Para o panorama completo das dores de operação, volte ao guia definitivo de PostgreSQL em produção. Se a sua topologia é MySQL, o raciocínio equivalente está em MySQL em aplicações críticas e escaláveis.
Sobre o autor: Alexandre Almeida atua com bancos de dados há mais de 25 anos, foi instrutor oficial de MySQL e Oracle para mais de 1.000 alunos e escreveu Aprendendo MySQL: Prática, Teoria e Laboratórios. Configurações validadas em PostgreSQL 16 e 17 com Patroni 4.
Fontes: PostgreSQL Documentation — High Availability, Load Balancing and Replication; Replication Slots; documentação oficial do Patroni; notas de release do PostgreSQL 13 a 17.