Configurando Replicação Síncrona para Alta Disponibilidade no PostgreSQL

Aprenda a configurar alta disponibilidade no PostgreSQL com perda zero de dados (RPO=0) usando replicação síncrona de streaming. Este tutorial passo a passo cobre configurações essenciais para `wal_level`, slots de replicação, `pg_basebackup` e a correta definição dos parâmetros `synchronous_commit` nos servidores primário e standby para garantir a durabilidade das transações em ambientes críticos.

Configurando Replicação Síncrona para Alta Disponibilidade no PostgreSQL

Configurar o PostgreSQL para alta disponibilidade (HA) geralmente começa com uma pergunta difícil: quanto de dados você pode perder se o servidor primário falhar logo após um commit? Com a replicação assíncrona normal, a resposta é "talvez alguns". O primário pode informar à aplicação que uma transação foi confirmada antes que o standby tenha recebido ou reproduzido o registro WAL. Se o primário falhar durante essa pequena janela, o standby promovido pode não conter as últimas transações confirmadas.

A replicação síncrona de streaming muda essa compensação. O PostgreSQL espera por um ou mais standbys nomeados antes de reportar o sucesso do commit. Dependendo do nível de synchronous_commit, o standby pode precisar apenas escrever o WAL no sistema operacional, descarregá-lo em armazenamento durável ou reproduzi-lo para que consultas no standby possam vê-lo. Isso pode lhe dar um RPO de zero para transações confirmadas, mas também significa que o caminho de escrita agora depende da rede e da saúde do standby.

Essa compensação é importante. A replicação síncrona é adequada para o pequeno conjunto de dados onde perder até mesmo uma transação reconhecida é inaceitável: pagamentos, saldos de contas, reservas de estoque, estado de pedidos, trilhas de auditoria. Muitas vezes é inadequada para logs de eventos de alto volume, dados de clique, métricas ou cargas de trabalho onde a disponibilidade e a latência importam mais do que a durabilidade perfeita entre nós. Antes de habilitá-la globalmente, decida qual parte da sua carga de trabalho realmente precisa dela.

Pré-requisitos

Antes de começar, certifique-se de ter dois servidores PostgreSQL configurados (Primário e Standby) executando versões principais idênticas do PostgreSQL. Ambos os servidores devem ter conectividade de rede. Para este guia, assumimos:

  • Hostname/IP do Primário: pg_primary
  • Hostname/IP do Standby: pg_standby
  • Usuário de Replicação: repl_user
  • Nome do Banco de Dados: mydb

Você também precisa de um backup funcional e uma janela de manutenção para o backup base inicial. Os exemplos assumem PostgreSQL 12 ou mais recente, onde o modo standby é controlado com standby.signal e as configurações de conexão são geralmente escritas pelo pg_basebackup -R.

Passo 1: Configurando o Servidor Primário

O servidor primário requer configurações específicas para habilitar a replicação de streaming e gerenciar o Write-Ahead Log (WAL) necessário para commits síncronos.

A. Ajustando postgresql.conf no Primário

Edite o arquivo postgresql.conf do servidor primário. Os seguintes parâmetros são obrigatórios para replicação de streaming:

# --- Obrigatório para Replicação ---
listen_addresses = '*'         # Permite conexões do standby
wal_level = replica            # Deve ser 'replica' ou superior (ex.: 'logical')
max_wal_senders = 10           # Máximo de conexões simultâneas de standbys
max_replication_slots = 10     # Slots necessários para streams de replicação persistentes

# --- Essencial para Commit Síncrono ---
synchronous_standby_names = 'FIRST 1 (standby1)' # Especifica os standbys necessários pelo application_name

# --- Opcional, mas Recomendado ---
wal_log_hints = on             # Recomendado para replicação mais segura, embora aumente o volume de WAL
shared_preload_libraries = 'pg_stat_statements' # Se usar monitoramento

Explicação dos Parâmetros Chave:

  • wal_level = replica: Isso garante que informações suficientes sejam escritas no WAL para permitir que um servidor standby reconstrua o estado do banco de dados. Para commits síncronos, este nível é o requisito mínimo.
  • synchronous_standby_names: Esta é a configuração central para definir quais standbys devem reconhecer as escritas. Os nomes aqui são valores application_name da conexão de replicação, não nomes de slots de replicação. FIRST 1 (standby1) significa que o PostgreSQL espera pelo primeiro standby síncrono disponível dessa lista. ANY 1 (standby1, standby2) significa que qualquer um dos standbys listados pode satisfazer o commit.

B. Configurando Autenticação Baseada em Host (pg_hba.conf)

O servidor primário deve permitir que o usuário de replicação do(s) servidor(es) standby se conecte para fins de replicação.

Adicione uma entrada ao pg_hba.conf no primário:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     repl_user       pg_standby/32           scram-sha-256

Substitua pg_standby/32 pelo endereço IP real ou sub-rede do seu servidor standby.

C. Criando o Slot de Replicação e o Usuário

Conecte-se ao PostgreSQL no servidor primário para criar o usuário necessário e o slot de replicação.

1. Criar Usuário de Replicação:

CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'uma_senha_forte';

2. Criar Slot de Replicação:

Este slot garante que os segmentos WAL sejam retidos até que o standby confirme o recebimento, evitando que o standby fique tão atrasado que precise de um novo backup base. Slots são úteis, mas também podem encher discos se um standby ficar inativo por muito tempo, então monitore o WAL retido.

SELECT pg_create_physical_replication_slot('standby1_slot');

O nome do slot não precisa corresponder a synchronous_standby_names. Neste exemplo, standby1 é o application_name usado para seleção de standby síncrono, enquanto standby1_slot é o slot de replicação física usado para retenção de WAL.

D. Reiniciando o Primário

Aplique todas as alterações de configuração reiniciando o serviço PostgreSQL no servidor primário.

sudo systemctl restart postgresql

Passo 2: Configurando o Servidor Standby

O servidor standby é configurado para transmitir registros WAL do primário usando uma configuração de recuperação.

A. Backup Base

Antes de iniciar o streaming, o standby precisa de uma cópia completa do diretório de dados do primário. Pare o PostgreSQL no standby primeiro.

sudo systemctl stop postgresql

Faça o backup base usando pg_basebackup. Substitua caminhos e detalhes de conexão conforme necessário:

# Exemplo usando o utilitário pg_basebackup
pg_basebackup -h pg_primary -D /var/lib/postgresql/15/main/ -U repl_user -P -Xs -R -W
  • -D: O diretório de dados de destino no standby.
  • -U: O usuário de replicação.
  • -P: Mostrar progresso.
  • -Xs: Incluir arquivos WAL necessários durante o backup base.
  • -R: Criar automaticamente o arquivo standby.signal e gerar as configurações de conexão necessárias em postgresql.auto.conf (ou configuração de recuperação).

B. Configurando postgresql.conf no Standby

No standby, certifique-se de que o PostgreSQL saiba como se conectar de volta ao primário. O detalhe chave para replicação síncrona é application_name; ele deve corresponder ao nome listado em synchronous_standby_names.

# --- Obrigatório no Standby ---
primary_conninfo = 'host=pg_primary port=5432 user=repl_user password=uma_senha_forte application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on          # Permite consultas de leitura durante o modo de recuperação/standby

C. Iniciando o Standby

Inicie o serviço PostgreSQL no servidor standby.

sudo systemctl start postgresql

Passo 3: Verificação e Teste do Commit Síncrono

Depois que ambos os servidores estiverem em execução, verifique a conexão e então teste o comportamento síncrono.

A. Verificando o Status da Replicação

Conecte-se ao banco de dados primário e verifique a visão pg_stat_replication:

SELECT client_addr, application_name, state, sync_state FROM pg_stat_replication;

Você deve ver uma entrada para standby1 com sync_state como sync. Se mostrar potential, o standby está conectado, mas não é atualmente o que está satisfazendo os commits síncronos. Se mostrar async, o PostgreSQL não está tratando-o como um standby síncrono; verifique a grafia de application_name e synchronous_standby_names.

B. Testando o Commit Síncrono

O parâmetro global que dita o quanto o PostgreSQL espera é synchronous_commit. Para RPO=0, você deve usar um valor que force a sincronização.

1. Definindo o Comportamento Global

Se você configurou synchronous_standby_names no primário como mostrado no Passo 1, o padrão synchronous_commit = on espera até que o standby síncrono tenha descarregado o registro WAL no armazenamento durável. remote_write espera até que o standby tenha escrito o registro WAL no sistema operacional, o que geralmente é mais rápido, mas não tão forte se o host standby falhar antes de descarregar. remote_apply espera até que o standby tenha reproduzido a transação, o que é útil quando sua aplicação lê do standby imediatamente após escrever no primário.

Para a maioria das configurações de HA com perda zero de dados, on é o ponto de partida prático. Use remote_apply apenas quando o comportamento de ler-após-escrever no standby for importante o suficiente para justificar a latência extra.

# Em postgresql.conf no Primário
synchronous_commit = on

Aviso: O commit síncrono pode aumentar visivelmente a latência de escrita em comparação com modos assíncronos (off ou local). A latência adicional vem de viagens de ida e volta na rede, velocidade de escrita WAL do standby e, para remote_apply, velocidade de reprodução.

2. Testando Dentro de uma Transação

Para testar transacionalmente (sem exigir uma alteração de configuração global), você pode defini-lo por sessão ou transação:

-- Conecte-se ao Primário

BEGIN;
SET LOCAL synchronous_commit = on;

INSERT INTO vendas (item, quantidade) VALUES ('Widget A', 100);
-- Este INSERT prepara WAL que deve ser reconhecido pelo standby síncrono.

COMMIT;
-- O COMMIT só é bem-sucedido após o standby reconhecer a escrita WAL.

Se nenhum standby síncrono configurado estiver disponível no momento do commit, o commit espera. Esse é o objetivo do recurso, mas pode surpreender as equipes durante uma falha: o primário pode ainda estar ativo, mas as escritas parecem congeladas porque o PostgreSQL está esperando por um reconhecimento síncrono. Não há uma configuração geral do PostgreSQL que automaticamente "caia" para commit assíncrono quando um standby síncrono desaparece. Se você deseja esse comportamento, suas ferramentas de HA devem alterar synchronous_standby_names, ou você deve ter um runbook para fazê-lo manualmente após decidir que a disponibilidade é mais importante que a perda zero de dados.

Um Padrão Mais Seguro com Dois Standbys

Um único standby síncrono oferece forte durabilidade enquanto tudo está saudável, mas também cria um ponto único de disponibilidade de escrita. Se esse standby estiver inativo, lento ou isolado do primário, os commits esperam. Em produção, um padrão comum é executar pelo menos dois standbys e exigir um reconhecimento síncrono:

synchronous_standby_names = 'ANY 1 (standby1, standby2)'

Com esta configuração, qualquer um dos standbys pode satisfazer o commit. Se standby1 estiver reiniciando, standby2 ainda pode reconhecer as escritas. Você ainda precisa monitorar ambas as réplicas, porque uma falha prolongada em um standby pode fazer com que seu slot de replicação retenha uma grande quantidade de WAL, mas o primário tem menos probabilidade de parar devido a uma única falha de standby.

Exigir dois reconhecimentos é possível:

synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'

Essa é uma escolha de durabilidade mais rigorosa. Geralmente é reservada para ambientes com links de latência muito baixa e uma razão clara para exigir mais de uma cópia remota antes do commit. Para muitos bancos de dados de aplicação, "qualquer um dos dois standbys próximos" é o melhor equilíbrio.

O que Monitorar Após Habilitá-lo

Não pare em "o standby conecta". A replicação síncrona pode estar tecnicamente funcionando enquanto a latência voltada ao usuário piora. Observe estes sinais após a implantação:

SELECT
    application_name,
    client_addr,
    state,
    sync_state,
    write_lag,
    flush_lag,
    replay_lag
FROM pg_stat_replication;

No primário, sync_state = 'sync' informa qual standby está atualmente síncrono. write_lag, flush_lag e replay_lag ajudam a explicar onde o tempo está sendo gasto. Se write_lag estiver alto, suspeite de rede ou pressão de escrita WAL no standby. Se flush_lag estiver alto, suspeite de armazenamento. Se apenas replay_lag estiver alto, o standby pode estar recebendo WAL, mas aplicando-o lentamente devido a E/S, CPU, locks ou consultas de longa duração no standby.

Monitore também a retenção de slots:

SELECT
    slot_name,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;

Um slot de replicação protege um standby, mas não protege seu disco. Se um slot estiver inativo e o WAL retido continuar crescendo, corrija o standby rapidamente ou remova o slot após confirmar que não precisa mais dele.

Um Plano de Implantação Prático

Para um sistema de produção movimentado, trate a replicação síncrona como uma mudança em etapas, em vez de um ajuste de configuração de uma linha.

Primeiro, construa o standby de forma assíncrona e deixe-o funcionar por um tempo. Confirme que ele consegue acompanhar durante períodos de pico de escrita. Se ele ficar atrasado assincronamente, prejudicará a latência de commit quando se tornar síncrono.

Segundo, defina application_name e verifique se o primário vê o standby exatamente como você espera em pg_stat_replication. Erros de ortografia são comuns aqui porque synchronous_standby_names corresponde ao application_name em tempo de execução, não ao hostname e não ao slot.

Terceiro, habilite a replicação síncrona durante uma janela de baixo tráfego e observe a latência de commit do lado da aplicação. As métricas do PostgreSQL podem parecer boas enquanto o pool de conexões da aplicação acumula porque as transações agora seguram as conexões por um pouco mais de tempo.

Finalmente, escreva a decisão de falha. Se o standby síncrono desaparecer e o primário estiver esperando por commits, quem está autorizado a relaxar synchronous_standby_names? Sob quais condições? Como você verificará se o primário antigo ou o standby antigo contém os dados mais recentes antes de religar os nós? Estas são decisões operacionais, não apenas configurações de banco de dados.

Melhores Práticas para HA Síncrona

  • Use Standbys Dedicados: Atribua apenas standbys que estão fisicamente próximos (baixa latência) ao primário à sua lista de replicação síncrona. A alta latência aparecerá diretamente no tempo de commit.
  • Monitore o Atraso de Replicação: Mesmo no modo síncrono, monitore o atraso do standby. Um standby lento que ainda está tecnicamente 'sync', mas demorando muito para processar WAL, ainda pode impactar a experiência do usuário.
  • Planeje a Compensação de Disponibilidade: Decida com antecedência se um operador pode remover temporariamente um standby ausente de synchronous_standby_names durante um incidente.
  • Use Múltiplos Standbys: Para melhor disponibilidade de escrita, configure synchronous_standby_names = 'ANY 1 (standby1, standby2)' para que qualquer um dos standbys possa reconhecer commits.