A replicação PostgreSQL no Node.js permite manter um ou mais servidores standby recebendo WAL do primary. As réplicas podem assumir o serviço durante falhas e, quando configuradas como hot standby, atender consultas somente leitura.
Replicação não transforma automaticamente a aplicação em altamente disponível. A API precisa distinguir reads e writes, tolerar lag, lidar com failover, atualizar pools e evitar enviar uma leitura crítica para uma réplica que ainda não recebeu a transação.
Neste guia, você aprenderá streaming replication, read replicas, lag, consistency, failover, replication slots, synchronous replication e padrões de conexão com Node.js.
Como a replicação física funciona?
A documentação oficial de warm standby e streaming replication explica que o primary gera WAL e o standby aplica esses registros continuamente. Streaming envia mudanças sem esperar o segmento WAL completar.
A replicação física copia o cluster em nível de armazenamento. Primary e standby normalmente precisam da mesma major version e arquitetura compatível.
Assíncrona por padrão
O primary confirma COMMIT antes de o standby aplicar a mudança. Existe uma janela de perda se o primary falhar. Também existe atraso para consultas de leitura.
Esse atraso pode ser milissegundos ou minutos, dependendo de rede, carga, I/O, queries longas e capacidade do standby.
Hot standby
Com hot_standby = on, a réplica aceita SELECT durante recovery. Ela continua somente leitura até ser promovida.
Conexões separadas
import pg from 'pg';
export const writerPool = new pg.Pool({
connectionString: process.env.DATABASE_WRITER_URL,
max: 10,
connectionTimeoutMillis: 3000,
application_name: 'orders-api-writer'
});
export const readerPool = new pg.Pool({
connectionString: process.env.DATABASE_READER_URL,
max: 20,
connectionTimeoutMillis: 3000,
application_name: 'orders-api-reader'
});O writer aponta ao primary. O reader aponta a um endpoint de réplicas ou load balancer read-only.
Escolhendo onde ler
Reads adequadas a réplicas:
- relatórios;
- dashboards com atraso aceitável;
- busca e listagens não críticas;
- exports;
- analytics operacional.
Reads que devem ir ao primary:
- imediatamente após escrita;
- autorização e revogação;
- estoque e saldo;
- idempotência;
- locks e coordenação;
- confirmação de pagamento.
Read-after-write
Este fluxo pode falhar:
- POST cria o pedido no primary.
- O cliente recebe 201.
- GET consulta a réplica.
- A réplica ainda não aplicou o WAL.
- A API retorna 404.
Uma estratégia é manter as leituras da sessão no primary por um período curto após uma escrita.
Sticky reads
function choosePool(context) {
if (context.requiresFreshRead || context.wroteRecently) {
return writerPool;
}
return readerPool;
}Não confie apenas em um cookie que o cliente possa remover para segurança. A regra de consistência deve estar ligada ao caso de uso.
LSN para consistência
Depois da escrita, obtenha a posição WAL:
const { rows } = await client.query(
'SELECT pg_current_wal_lsn() AS lsn'
);Na réplica, compare com:
SELECT pg_last_wal_replay_lsn();A aplicação pode esperar até o replay alcançar o LSN, com timeout curto, e cair para o primary se necessário. Isso aumenta complexidade; use somente em fluxos que precisam.
Medindo lag
No primary:
SELECT
application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes
FROM pg_stat_replication;Na réplica:
SELECT
now() - pg_last_xact_replay_timestamp() AS replay_delay,
pg_is_in_recovery() AS is_replica;Lag em tempo pode ser NULL quando não há transações recentes. Combine bytes, timestamps e estado.
Routing por lag
Um health check do reader pode rejeitar a réplica acima do limite:
const result = await readerPool.query(`
SELECT EXTRACT(EPOCH FROM (
now() - pg_last_xact_replay_timestamp()
)) AS lag_seconds
`);
if (Number(result.rows[0].lag_seconds ?? 0) > 5) {
throw new ReplicaLagError();
}O limite depende da função. Um dashboard pode aceitar trinta segundos; autorização talvez aceite zero.
Consultas canceladas no standby
Uma query longa pode conflitar com WAL que remove linhas ou altera estruturas. PostgreSQL pode cancelar a consulta para continuar recovery:
ERROR: canceling statement due to conflict with recoveryA aplicação deve reconhecer o erro, limitar duração e, quando seguro, tentar outra réplica ou o primary.
hot_standby_feedback
Essa opção informa ao primary quais linhas ainda são necessárias na réplica, reduzindo cancelamentos. Porém, pode atrasar VACUUM e causar bloat no primary. Monitore dead tuples e transações longas.
max_standby_streaming_delay
Controla quanto o standby pode atrasar replay antes de cancelar queries conflitantes. Aumentar favorece consultas, mas aumenta lag e RPO no failover.
Replication slots
Slots impedem o primary de remover WAL antes de a réplica recebê-lo:
SELECT pg_create_physical_replication_slot('replica_a');No standby:
primary_slot_name = 'replica_a'Uma réplica desligada pode fazer o primary acumular WAL até encher o disco. Monitore pg_replication_slots e configure limites como max_slot_wal_keep_size.
WAL archive
Arquivamento contínuo permite recuperar segmentos que não estão mais no primary e também suporta Point-in-Time Recovery. O archive deve continuar acessível mesmo se o primary falhar.
Synchronous replication
Com synchronous_standby_names, commits aguardam confirmação de uma ou mais réplicas:
synchronous_standby_names = 'ANY 1 (replica_a, replica_b)'Isso reduz perda de dados, mas adiciona latência e pode bloquear writes se não houver standby elegível.
synchronous_commit
O nível pode ser definido por transação:
BEGIN;
SET LOCAL synchronous_commit = 'remote_apply';
UPDATE payments SET status = 'confirmed' WHERE id = $1;
COMMIT;remote_apply espera a réplica aplicar o commit, permitindo leitura causal em cenários simples. O custo inclui round trip e replay.
Durabilidade seletiva
Use maior garantia para pagamentos e menor para eventos reconstruíveis. A decisão deve ser explícita e documentada, não escolhida aleatoriamente por endpoint.
Failover
Promover um standby:
SELECT pg_promote();Em produção, um orchestrator deve escolher a melhor réplica, aplicar fencing no primary antigo, atualizar endpoints e reconfigurar standbys.
Fencing
Antes da promoção, impeça o primary antigo de aceitar writes. Sem fencing, uma partição pode criar dois primaries e divergência.
DNS e endpoints
Uma abordagem usa endpoints separados:
postgres-writer.internal
postgres-reader.internalApós failover, o writer muda para o novo primary. Configure TTL, resolução e lifetime das conexões para não manter sockets ao servidor antigo indefinidamente.
Pool e failover
Conexões existentes não mudam quando o DNS muda. Defina maxLifetimeSeconds, trate erros fatais e recrie pools se a plataforma sinalizar failover.
Consulte Pool PostgreSQL no Node.js e PgBouncer no Node.js.
Retries após failover
Uma transação pode ter sido commitada antes de a conexão cair. Repetir cegamente pode duplicar efeitos. Use idempotency keys e verificação de estado. Veja Idempotência em APIs Node.js.
Transações read-only
BEGIN READ ONLY;
SELECT ...;
COMMIT;Isso documenta intenção e impede writes acidentais. A réplica já rejeita writes, mas o mesmo código pode rodar no primary durante fallback.
Load balancing de readers
Distribua por capacidade e lag. Round-robin simples pode enviar queries a uma réplica atrasada ou em recovery lento. Health checks precisam incluir replay e capacidade.
Réplicas para jobs
Exports e relatórios pesados podem competir com replay e aumentar lag. Limite concorrência, statement timeout e memória. Para analytics intenso, use um sistema dedicado.
Migrations
Migrations executam no primary e são replicadas. Alterações de schema podem cancelar queries nas réplicas e gerar incompatibilidade durante rollout. Use expand-and-contract. Consulte Migrações de Banco no Node.js.
Backups
Uma réplica não substitui backup. DROP TABLE e dados corrompidos também são replicados. Mantenha base backups, WAL archive e testes de restore.
Segurança
O usuário de replicação possui privilégio elevado para ler WAL. Use uma conta dedicada, SCRAM, TLS e regras restritas no pg_hba.conf:
hostssl replication replicator 10.0.2.15/32 scram-sha-256Proteja certificados e senhas. Veja Gestão de Segredos no Node.js.
Observabilidade
Monitore:
- estado streaming, catchup e startup;
- lag em bytes e tempo;
- WAL retido por slots;
- espaço em pg_wal;
- queries canceladas por recovery;
- tempo de failover;
- connections ao primary e replicas;
- erros read-only e stale reads.
Teste de failover
- gere writes e reads;
- registre LSN e valores;
- isole o primary;
- aplique fencing;
- promova o standby;
- atualize writer endpoint;
- confirme pools reconectados;
- meça perda e indisponibilidade;
- recrie a réplica antiga.
Erros comuns
- Todas as reads na réplica: read-after-write retorna dado antigo.
- Slot sem monitoramento: WAL enche o disco.
- Failover sem fencing: dois primaries aceitam writes.
- DNS sem reciclar pools: clientes permanecem no host antigo.
- Retry de transação desconhecida: efeitos são duplicados.
- hot_standby_feedback sem observar bloat: VACUUM fica prejudicado.
- Réplicas como backup: exclusões também são copiadas.
- Lag ignorado: decisões usam estado obsoleto.
Conclusão
A replicação PostgreSQL no Node.js oferece standbys para alta disponibilidade e leitura. Streaming replication mantém as réplicas próximas do primary, mas o atraso e a replicação assíncrona precisam fazer parte do design da aplicação.
Separe writer e readers, direcione leituras críticas ao primary e monitore LSN, lag e slots. Teste failover com fencing e trate conexões e retries de forma idempotente. Assim, a replicação aumenta disponibilidade sem introduzir inconsistência silenciosa.



