trilha

Tecnologias/02 - Bancos de Dados/01 - Relacionais/01 - PostgreSQL6 min

WAL, Checkpoint e Replicação

Conceito

O WAL (Write-Ahead Log) resolve um problema aparentemente impossível: como garantir durabilidade sem escrever no disco, de forma aleatória, todas as páginas que uma transação tocou?

A resposta é uma inversão:

Não grave a mudança onde o dado mora. Grave a intenção num log sequencial, e confirme o COMMIT quando o log estiver seguro. As páginas de dados vão para o disco depois, com calma.

COMMIT
  ↓
1. escreve o registro no WAL (arquivo sequencial, append-only)
2. fsync do WAL                       ← É AQUI que o COMMIT termina
3. responde "ok" ao cliente
   ...
4. minutos depois: background writer / checkpointer gravam as páginas de dados

Uma escrita sequencial de alguns bytes substitui N escritas aleatórias de 8 KB. Se o servidor cair entre 3 e 4, o WAL é reexecutado (replay) na inicialização e nada é perdido. É o mesmo princípio do redo log do InnoDB e do Oracle — e, curiosamente, o mesmo princípio do log do Kafka.

LSN: o endereço de tudo

Cada registro do WAL tem um LSN (Log Sequence Number), tipo 0/16B3B48 — um offset monotônico no fluxo de WAL. Ele é a régua universal do Postgres:

  • cada página de dados guarda o LSN da última mudança que a afetou;
  • a réplica reporta "apliquei até o LSN X" (é assim que se mede lag em bytes);
  • PITR usa LSN/timestamp como alvo de recuperação.
SELECT pg_current_wal_lsn();                                -- onde o primário está
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)    -- lag em BYTES, não em segundos
  FROM pg_stat_replication;

Checkpoint: a dívida que precisa ser paga

O WAL só pode ser descartado depois que as páginas correspondentes chegarem ao disco. O checkpoint é esse acerto de contas:

  1. Marca um ponto no WAL.
  2. Força todas as páginas sujas do shared_buffers para o disco.
  3. Grava o pg_control dizendo "a recuperação pode começar daqui".

Dois gatilhos: tempo (checkpoint_timeout, padrão 5 min) e volume (max_wal_size).

Checkpoint mais frequente Checkpoint mais espaçado
recuperação rápida após crash recuperação mais longa
picos de I/O frequentes menos I/O total (uma página suja 10× é escrita 1×)
menos WAL acumulado mais espaço em pg_wal

O ajuste padrão para OLTP é espaçar (max_wal_size grande) e espalhar (checkpoint_completion_target = 0.9, que dilui a escrita ao longo de 90% do intervalo).

full_page_writes e a amplificação silenciosa

Na primeira modificação de uma página após um checkpoint, o Postgres grava a página inteira (8 KB) no WAL, não só o delta — proteção contra torn page (escrita parcial). Por isso o volume de WAL sobe muito logo após cada checkpoint, e por isso wal_compression = on costuma valer a pena.

Replicação: dois mecanismos diferentes

Física (streaming) — a réplica recebe o WAL bruto e o reexecuta:

primário ──[WAL bytes]──> walsender → walreceiver → replay na réplica
  • Cópia byte a byte: todo o cluster, mesma versão major, réplica somente leitura.
  • Baixíssima latência; é o que RDS/Aurora e Patroni usam.
  • synchronous_commit = on|remote_apply|off decide se o COMMIT espera a réplica.

Lógica — a réplica recebe mudanças de linha decodificadas (via logical decoding):

primário → slot de replicação → plugin (pgoutput) → INSERT/UPDATE/DELETE lógicos
  • Por tabela, entre versões diferentes, para outro Postgres ou para Kafka/Debezium.
  • É o mecanismo de CDC e a base do outbox automatizado (1. Mensageria e Desacoplamento).
  • Custo: precisa de PK ou REPLICA IDENTITY, não replica DDL, e o slot é perigoso.
Física Lógica
Granularidade cluster inteiro tabela
Réplica escrevível não sim
Versões diferentes não sim
Uso típico HA, réplica de leitura CDC, migração, integração
DDL replicado (é byte) não

Replication slots: o pé no freio que enche o disco

Um slot garante que o primário não descarte WAL que um consumidor (réplica ou Debezium) ainda não leu. É exatamente o que você quer — e é o modo mais rápido de encher o disco do primário:

Consumidor lógico parado + slot ativo = pg_wal crescendo até 100% do disco = banco parado.

Sempre configure max_slot_wal_keep_size (Postgres 13+) para que o slot seja invalidado antes de o disco morrer. Perder a réplica é ruim; perder o primário é pior.

Trade-offs

synchronous_commit é o botão durabilidade × latência. Com off, o COMMIT não espera o fsync do WAL: você ganha muito throughput e aceita perder até wal_writer_delay de transações num crash (sem corromper nada — só perde as últimas). Para telemetria, é uma troca ótima. Para financeiro, não.

Replicação síncrona multiplica a latência e pode parar o primário. Com synchronous_standby_names e uma única réplica síncrona, se a réplica cair o primário para de aceitar escrita (ele está esperando alguém confirmar). O desenho correto é ANY 1 (r1, r2) com duas réplicas — senão você transformou HA em ponto único de falha. É Availability vs Consistency em uma linha de configuração.

PITR é a única defesa contra erro humano. Réplica não protege de DELETE sem WHERE: o comando é replicado em milissegundos. Só backup base + WAL archive permite voltar a "10 segundos antes do incidente".

O trade-off central: o WAL transforma escrita aleatória em sequencial, e o preço é que tudo passa a depender do WAL: durabilidade, recuperação, réplicas e CDC. Quem não gerencia o volume, o arquivamento e os slots do WAL não gerencia o Postgres.

Exemplo prático

Arquivamento e PITR — o mínimo para um banco de produção:

wal_level = replica              # 'logical' se você for usar CDC/Debezium
archive_mode = on
archive_command = 'pgbackrest --stanza=prod archive-push %p'
max_wal_size = 4GB
checkpoint_completion_target = 0.9
wal_compression = on
max_slot_wal_keep_size = 64GB    # o slot morre antes do disco
# recuperar até 1 minuto antes do DELETE fatídico
pgbackrest --stanza=prod --type=time \
  --target="2026-09-08 14:31:00" --target-action=promote restore

Monitoramento essencial de replicação:

-- lag por réplica, em bytes e em tempo
SELECT client_addr, state, sync_state,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS lag_bytes,
       replay_lag
  FROM pg_stat_replication;

-- slots: 'active = false' com lag alto é o alerta que salva o disco
SELECT slot_name, plugin, active,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_retido
  FROM pg_replication_slots ORDER BY 4 DESC;

Como isso falha em produção

Sintoma Causa
pg_wal enchendo o disco slot inativo, ou archive_command falhando (o WAL não pode ser descartado)
Primário parou de aceitar escrita única réplica síncrona ficou indisponível
Latência de escrita com picos de 30 s checkpoint concentrado; max_wal_size pequeno
Réplica cancelando queries longas conflito entre replay e snapshot: hot_standby_feedback ou max_standby_streaming_delay
Réplica não acompanha nunca replay é single-threaded; escrita do primário maior que o I/O da réplica
CDC "pulou" mudanças slot recriado, ou wal_level não era logical
Restore não funciona ninguém testou o restore. Backup não testado não é backup.

Aprofundamento aplicado

Confirmar, replicar e aplicar são marcos diferentes

Uma gravação pode estar confirmada no primário e ainda não estar visível em uma réplica. A réplica precisa receber WAL, persistir conforme a garantia exigida e reaplicar os registros para expor o estado correspondente. O nível de confirmação síncrona escolhido influencia qual desses marcos participa da espera do commit. Referência: replicação síncrona.

Cenário: o cliente cria um pedido e é direcionado a uma réplica para consultá-lo. Uma resposta “não existe” pode refletir atraso, e não perda. Opções incluem ler no primário nessa jornada ou coordenar a leitura com a posição necessária, conforme a arquitetura.

Dimensionar retenção pelo ritmo de escrita

Se o WAL cresce a 20 MiB/s e um consumidor fica parado por uma hora, a ordem de grandeza do volume produzido é 72.000 MiB, antes de considerar outros limites e causas de retenção. Uma política de slots precisa ligar atraso, espaço disponível e estratégia de ressincronização.

Exercício: desenhe a falha do primário antes e depois de a réplica persistir o WAL. Resultado esperado: explique quais confirmações podem se perder na configuração assíncrona e por que promover uma réplica exige impedir escritas no antigo primário.

Relacionado


Parte de MOC PostgreSQL · WAL (docs)

Buscar

Busca por título, seção e texto das notas