Por que max_wal_size do PostgreSQL não é um limite máximo nem o tamanho do WAL
O parâmetro que todo mundo lê como um teto de espaço na verdade é um gatilho de checkpoint. Entender a conta por trás dele evita tempestades de I/O e recovery mais longo do que deveria.

O parâmetro que todo mundo lê como um teto de espaço na verdade é um gatilho de checkpoint. Entender a conta por trás dele evita tempestades de I/O e recovery mais longo do que deveria.
O nome engana, a mecânica não
max_wal_size existe desde a versão 9.5 do PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data →, quando Heikki Linnakangas substituiu o antigo checkpoint_segments. Apesar do nome, o parâmetro não define um tamanho máximo para nada que se meça com du no diretório pg_wal. Ele é, como descreve o autor do post publicado no Planet PostgreSQL, um gatilho de checkpoint denominado em bytes.
O mecanismo é simples de enunciar: quando o WAL escrito desde o início do último checkpoint atinge uma fração fixa desse valor, o próximo checkpoint começa, com ou sem o checkpoint_timeout ter vencido. O default é 1GB, o contexto é sighup (muda sem reiniciar o servidor) e a faixa vai de 2 a 2147483647 megabytes, cerca de dois petabytes que, segundo o autor, ninguém testou de verdade.
checkpoint_segments, o parâmetro antigo, tinha default 3 e disparava um checkpoint a cada 48 MB de WAL desde a versão 7.1, em 2001. A fórmula de conversão dada no release notes da 9.5 foi (3 * checkpoint_segments) * 16MB. O novo default já nascia bem mais alto, o que era verdade e, ao mesmo tempo, uma vara muito baixa para pular.
A conta que o checkpointer realmente faz
O checkpointer não dispara em max_wal_size. Ele dispara em max_wal_size / (1 + checkpoint_completion_target), arredondado para segmentos inteiros de 16 MB, porque o parâmetro define o WAL máximo entre o ponto de redo de um checkpoint e o fim do próximo, e o servidor continua escrevendo WAL enquanto um checkpoint espalhado no tempo está em curso.
Com os defaults do PostgreSQL 14 em diante (checkpoint_completion_target = 0.9), isso dá 64 / 1.9 = 33 segmentos, ou 528 MB. O número aparece em toda linha de log_checkpoints:
checkpoint complete: wrote 36828 buffers (56.2%), ... 0 WAL file(s) added, 0 removed, 33 recycled; write=22.929 s, sync=0.150 s, total=23.108 s; ... distance=540676 kB, estimate=540681 kB; ...distance é o WAL escrito entre os dois últimos inícios de checkpoint, estimate é a média móvel que o servidor usa para decidir quantos segmentos manter, e os dois batem em 33 × 16 MB. O mesmo valor de max_wal_size já produziu três gatilhos diferentes ao longo das versões, sem que o número configurado mudasse uma vírgula.
- Antes da 11: o servidor guardava WAL para dois ciclos de checkpoint e dividia por
2 + checkpoint_completion_target. Com o default antigo de 0.5,1GBvirava um checkpoint a cada 400 MB. - Da 11 à 13, com o mesmo
checkpoint_completion_target = 0.5, a fórmula passou a dividir por1 + completion_target, o que elevou o intervalo para 672 MB. - Da 14 em diante, com o default do
completion_targetsubindo para 0.9, o intervalo caiu para os 528 MB do exemplo acima.
O que apertar demais custa, em números
O autor rodou o mesmo workload de pgbench (escala 50, quatro clientes, shared_buffers em 512 MB, três minutos por rodada, checkpoint_timeout em 15min, PostgreSQL 18.6) variando só max_wal_size:
| Configuração | tps | WAL escrito | checkpoints | wal_fpi |
|---|---|---|---|---|
max_wal_size = 1GB | 3.700 | 3.815 MB | 7 (forçados) | 447.963 |
max_wal_size = 8GB | 4.131 | 1.080 MB | 0 | 91.335 |
São três vezes e meia mais WAL para as mesmas transações, e a diferença real é maior do que o número sugere: a rodada de 8GB ainda pagou, nos três minutos, a reimagem de página de um checkpoint inteiro, custo que um ciclo de quinze minutos paga uma vez só. O mecanismo por trás disso é o full_page_writes: a primeira mudança em qualquer página depois de um checkpoint grava a página de 8 kB inteira no WAL, e a 1GB esse workload disparava um checkpoint a cada 22 a 28 segundos, reimageando as páginas quentes com essa frequência.
pg_stat_wal.wal_fpi é o lugar certo para enxergar isso no próprio servidor: a 1GB, imagens de página inteira eram 10% dos registros de WAL e, com wal_compression desligado, cerca de 90% dos bytes gravados. Cada byte a mais de WAL também é o que será arquivado, replicado para cada réplica e lido por cada slot de decodificação lógica, o que torna o parâmetro relevante para qualquer pipeline de CDC construído sobre pgoutput ou wal2json. E o log não ficou quieto: a mensagem checkpoints are occurring too frequently (23 seconds apart), com dica apontando para o próprio max_wal_size, apareceu sete vezes na rodada de 1GB.
As duas mentiras do nome
A primeira mentira é que o parâmetro não é o tamanho de pg_wal. Ao final de um checkpoint, o servidor remove segmentos mais antigos que o novo ponto de redo, mas recicla os primeiros em segmentos futuros pré-alocados, mantendo entre min_wal_size e max_wal_size de WAL a partir daquele ponto de redo, com base na estimativa móvel. Sob carga de escrita constante, pg_wal fica perto de max_wal_size, o que explica a confusão, mas o limite é soft em uma direção só: slot de replicação inativo, archive_command falhando ou wal_keep_size configurado seguram segmentos já escritos, e nada em max_wal_size impede isso (esse é trabalho de max_slot_wal_keep_size e de monitorar os outros dois).
O limite também é soft no sentido oposto, de um jeito menos óbvio: baixar o valor não libera espaço na hora. Segmentos já pré-alocados à frente do ponto de inserção não são revisitados por um checkpoint. No teste do autor, baixar a configuração de 4GB para 1GB e rodar três checkpoints deixou pg_wal em 2,9 GB, com 184 segmentos esperando para serem escritos; o valor só convergiu para 1 GB depois de cerca de 1,9 GB de WAL novo, um checkpoint por vez, à medida que cada ciclo removia segmentos antigos em vez de reciclá-los.
A segunda mentira é a palavra "máximo" em si, que sugere um custo por ultrapassar o valor quando o custo de verdade é pago por ficar abaixo dele.
O custo real é pago por ficar abaixo do limite, não por passar dele.
The real cost is paid by going under.autor do post no Planet PostgreSQL
Depois de um crash, a recuperação reaplica o WAL a partir do último checkpoint completo, e se a queda acontece perto do fim do checkpoint seguinte, isso é a distância inteira do gatilho mais tudo que foi escrito durante o checkpoint: um max_wal_size completo. O autor mediu: matar o postmaster com 1.080 MB de WAL desde o último checkpoint custou 6,0 segundos de redo; com 1.627 MB, 12,5 segundos; e 682 MB de WAL do regime de 1GB, carregado de imagens de página inteira, levou só 1,5 segundo, porque restaurar uma imagem de página é uma cópia, enquanto um registro comum exige ler a página antes de aplicar a mudança.
Isso dá algo como 10 a 12 segundos por gigabyte de WAL comum, single-threaded, com os dados em cache, na máquina usada no teste. Com max_wal_size = 8GB, o pior caso ali seria um minuto e meio de recovery; com 16GB, três minutos.
O que fazer com isso na prática
O autor propõe um procedimento de ajuste em vez de um número mágico:
- Configure
checkpoint_timeout = 15minemax_wal_size = 8GBe deixe rodar por uma semana. - Procure
checkpoint starting: walno log fora de janelas de carga em lote. - Se aparecer fora de bulk load, dobre
max_wal_sizee repita a busca. - Se aparecer só durante a importação noturna, é exatamente para isso que o parâmetro existe; não mexa.
Quem tem um orçamento de tempo de recovery definido (um RTO contratual, por exemplo) divide esse orçamento pelos segundos por gigabyte medidos no próprio hardware, e esse é o teto prático de max_wal_size. A proporção de imagens de página inteira muda essa conta de servidor para servidor, então o número medido pelo autor serve como ordem de grandeza, não como tabela a copiar.
Para quem opera PostgreSQL gerenciado (RDS, Cloud SQL, instâncias próprias em Kubernetes↳Kubernetes18 conteúdosGerenciamento de infraestrutura multicloud com Kubernetes proporciona otimização e economiaDevSecOps · set 2021Azure Kubernetes Services – AKS: referências gratuitas e dicas para solução de problemas comunsDevSecOps · abr 2019Kubernetes em produção: o que ninguém te conta e você aprende tarde demaisDev (Back & Front) · abr 2026Ver tudo em DevSecOps →), o ângulo prático é outro: disco de WAL é, segundo o autor, "a coisa mais barata do prédio". Dado o impacto em réplicas, em archive_command e em qualquer consumidor de decodificação lógica, vale tratar max_wal_size como parâmetro de arquitetura, não como ajuste fino de última hora antes de um pico de tráfego.
Fonte: Planet PostgreSQL
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
Repositório RPM do PostgreSQL ganha site mais simples de configurar
Devrim Gündüz, mantenedor dos repositórios PGDG para YUM e ZYPP, anunciou nesta segunda-feira (28) uma reformulação do site que gera comandos de instalação prontos para copiar, reduzindo o risco de escolher a combinação errada de sistema operacional e versão do PostgreSQL.















