Parâmetro recovery_prefetch do Postgres pode travar réplicas síncronas por horas
Investigação de Jeremy Schneider mostra como o recurso de prefetch, ativado por padrão em versões recentes do PostgreSQL, pode converter gravações sequenciais em leituras aleatórias e travar clusters com replicação síncrona.
Um ajuste de replicação ativado por padrão em versões recentes 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 → pode converter gravações sequenciais em leituras aleatórias e travar um cluster inteiro com replicação síncrona, segundo investigação publicada por Jeremy Schneider em 20 de outubro.
Jeremy Schneider, que assina o blog Ardent Performance Computing, publicou em 20 de outubro um relato técnico que liga um parâmetro aparentemente inofensivo do PostgreSQL a incidentes reais de produção: o recovery_prefetch. O texto não é especulação. Schneider descreve como reproduziu, de forma consistente, uma trava completa de escritas que pode durar de 30 minutos a várias horas em clusters com replicação síncrona.
O problema ficou invisível por anos porque cada sintoma isolado parecia pequeno demais para investigar a fundo. Juntando as peças, Schneider mostra que a combinação de três fatores comuns em produção (Full Page Writes ligados, replicação síncrona e um parâmetro de prefetch ativo por padrão) forma uma receita de outage.
O que o recovery_prefetch faz e por que existe
O recovery_prefetch foi introduzido no PostgreSQL para acelerar o replay de WAL em standbys. A ideia é simples: antes de aplicar um registro de WAL que exige ler um bloco do disco, o processo de replay dispara uma chamada posix_fadvise para esse bloco com antecedência, aproveitando o tempo de I/O em paralelo em vez de bloquear serialmente a cada leitura.
Por padrão, o recurso fica ligado sempre que o sistema operacional suporta fadvise. Para replicas que precisam alcançar o primário depois de ficarem para trás, isso reduz o tempo de catch-up. Até aqui, o racional é defensável: menos tempo de replay atrasado é menos tempo de lag visível para quem monitora a réplica.
Onde o prefetch quebra: FPI e amplificação de IOPS
O primeiro sinal de problema veio de dentro da própria comunidade, antes mesmo do recurso ser mesclado. Em fevereiro de 2021, Stephen Frost levantou uma ressalva na lista de discussão dos hackers do Postgres sobre o comportamento do prefetch quando Full Page Writes (FPW) estão habilitados, o que hoje é a imensa maioria das instalações em produção, já que é a única proteção nativa do Postgres contra torn writes em reinícios do sistema operacional.
Emitir prefetches desnecessários para blocos que já enviamos prefetch antes é discutivelmente um problema mesmo com FPWs desligados, mas o benefício do prefetch quase certamente supera isso, e encontrar uma forma de resolver é algo que poderíamos fazer depois, como melhoria futura. Não teria problema com isso. Só não parece tão claro quando penso no caso com FPW habilitado. No fim, se você, o Andres e o Munro não estão preocupados e ninguém mais se manifestar, eu não vou brigar por isso ser commitado, mas, como você disse, parecia um bom ponto para todo mundo considerar.
Issuing unnecessary prefetches for blocks we've already sent a prefetch for is arguably a concern even if FPWs are off but the benefit of doing the prefetching almost certainly will outweight that and mean that finding a way to address it is something we could certainly do later as a future improvement. I wouldn't have any issue with that. Just doesn't seem as clear-cut to me when thinking about the FPW-enabled case. Ultimately, if you, Andres and Munro are all not concerned about it and no one else speaks up then I'm not going to pitch a fuss over it being committed, but, as you said above, it seemed like a good point to raise for everyone to consider.Stephen Frost
A ressalva foi registrada, mas o recurso seguiu em frente sem mudanças nesse ponto. Quatro anos depois, em março de 2025, Michael Zhilin, da Postgres Pro, relatou em outra thread um caso de cliente real: um standby físico em hardware potente acumulando horas de lag de replay, com o processo startup consumindo CPU majoritariamente em espaço de kernel.
Um cliente reclamou que o standby físico tinha lag de replay significativo (algumas horas) em servidores bare metal modernos e potentes. Segundo a saída do top, o processo startup e alguns workers de kernel consumiam CPU, principalmente em espaço de kernel. O incidente foi resolvido desabilitando o recovery_prefetch, após uma análise rápida das saídas de perf e strace. O código real de prefetch de XLog não considera que um shared buffer pode ser carregado a partir de um FPI do WAL em vez do disco, e faz chamadas fadvise mesmo nesse caso.
One customer complained that physical standby has significant replay lag (few hours) on modern powerful bare metal servers. According to output of "top", startup process and few kernel workers utilized CPU, mostly in kernel space. The incident has been resolved by disabling recovery_prefetch after quick analysis of perf and strace outputs. Actual XLog prefetching code doesn't consider that shared buffer may be loaded from WAL FPI rather than from disk and makes fadvise calls in such cases.Michael Zhilin, Postgres Pro
Schneider argumenta que os dois relatos capturaram sintomas reais, mas não a causa completa. O terceiro elemento, que ele diz não ter sido diagnosticado antes, é que o prefetch também converte I/O sequencial grande no primário em leituras aleatórias de 8 KB no standby. Para o mesmo número de bytes lidos, isso pode amplificar o consumo de IOPS em até 16 vezes na réplica, segundo os números apresentados no relato.
Por que isso vira outage e não só lentidão
O efeito devastador aparece quando esse padrão de IOPS encontra replicação síncrona. Com synchronous_commit dependendo de pelo menos um standby saudável, o primário não confirma transação alguma sem a confirmação remota. É o preço de garantir durabilidade sem depender de replicação de storage proprietária.
O núcleo do comportamento está na máquina de estados definida em src/backend/access/transam/xlogrecovery.c, responsável por decidir de onde o standby lê o próximo WAL:
/*-------
* Standby mode is implemented by a state machine:
*
* 1. Read from either archive or pg_wal (XLOG_FROM_ARCHIVE), or just
* pg_wal (XLOG_FROM_PG_WAL)
* 2. Check for promotion trigger request
* 3. Read from primary server via walreceiver (XLOG_FROM_STREAM)
* 4. Rescan timelines
* 5. Sleep wal_retrieve_retry_interval milliseconds, and loop back to 1.
* Failure to read from the current source advances the state machine to
* the next state.
*/Quando o walreceiver cai, por instabilidade de rede, por exemplo, o standby muda para o modo de restore via WAL local e só volta a aceitar streaming depois de recuperar o atraso por completo. Se nesse meio tempo uma rajada de escrita (uma UPDATE em massa, um VACUUM pesado) empurrou IOPS amplificados para a réplica, o tempo para ela alcançar o primário pode se estender por horas, com o primário bloqueado o tempo todo.
A reprodução: de 5 TPS a zero em poucos minutos
Schneider publicou um roteiro de reprodução no repositório ardentperf-labs/walreceiver-outage-repro, no GitHub. Os passos descritos no relato são:
- Subir o Postgres com replicação síncrona para um standby, em servidor pequeno com disco de nuvem barato (500 IOPS, 2 CPUs, 8 GB de memória).
- Inicializar o
pgbenchcomscale640, cerca de 10 GB de dados. - Desabilitar o autovacuum em
pgbench_accountspara não interferir no experimento. - Rodar
pgbencha 5 TPS fixos num terminal visível. - Atualizar 5% da tabela (
UPDATE pgbench_accounts WHERE aid % 20 = 0), gerando uma rajada de escrita; esperar terminar. - Matar o processo
walreceiverpara simular uma falha ou instabilidade de rede.
Com recovery_prefetch no padrão, o TPS cai a zero e as escritas ficam travadas por mais de 30 minutos, podendo chegar a horas com uma tabela maior. Os gráficos de disco mostram o primário em torno de 300 IOPS durante a rajada, enquanto o standby fica preso no teto de 500 IOPS por 40 minutos processando o mesmo volume de bytes.
Com recovery_prefetch = off (e FPW ainda ligado), o mesmo teste mostra um comportamento bem diferente:
| Cenário | recovery_prefetch ligado | recovery_prefetch desligado |
|---|---|---|
| IOPS no standby durante a rajada | no teto do disco (500), por ~40 min | sem pico de leitura aleatória |
| Tempo para o standby alcançar o primário | horas, dependendo do tamanho da tabela | menos de 10 minutos |
| Impacto no primário | escritas bloqueadas enquanto o standby não reconecta | pausa apenas durante a própria UPDATE |
Schneider resume a conclusão prática: "Right now, I'm leaning toward disabling the recovery_prefetch setting across the board, but I want to see where the discussions go", ou seja, ele próprio já está inclinado a recomendar desligar o parâmetro de forma geral, mas aguarda o desenrolar da discussão na comunidade antes de uma recomendação definitiva.
Como diagnosticar isso num ambiente real
Para quem administra Postgres com replicação síncrona, vale montar um checklist de observação antes de esperar um incidente:
- Correlacionar picos de
replay_lagempg_stat_replicationcom janelas deUPDATE/DELETEem massa ou execuções pesadas deVACUUM. - Rodar
iostatno standby durante essas janelas e verificar se o dispositivo de disco bate no teto de IOPS contratado, mesmo com volume de bytes comparável ao do primário. - Usar
topestraceno processostartupdo standby quando houver lag persistente, replicando o diagnóstico que Michael Zhilin aplicou no caso da Postgres Pro: tempo de CPU concentrado em espaço de kernel é um sinal de chamadasfadviseem excesso. - Testar deliberadamente a queda do
walreceiverem ambiente de homologação com uma carga de escrita realista, usando o roteiro publicado por Schneider como ponto de partida.
O que fazer e o que isso custa
A mitigação mais direta é desabilitar o recovery_prefetch (recovery_prefetch = off) em instâncias que rodam replicação síncrona sobre storage com IOPS limitado, caso comum 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 → com disco em nuvem. O trade-off é perder parte do ganho original do recurso: standbys que ficam para trás por outros motivos (manutenção, rede lenta) vão demorar mais para recuperar o atraso, porque voltam a depender de leitura serial sem prefetch.
Schneider também sugere revisitar a topologia de replicação em vez de só mexer no GUC. Rodar com dois standbys e quorum commit (ANY 1) em vez de depender de um único standby síncrono reduz a chance de o primário ficar completamente bloqueado quando um walreceiver cai: basta que um dos dois reconecte a tempo. O custo é operar com três réplicas no total em vez de duas, o que pesa no orçamento de infraestrutura.
Enquanto a comunidade do Postgres não define um encaminhamento definitivo para o comportamento do prefetch, a decisão fica por conta de cada time: medir o próprio padrão de escrita, verificar o teto de IOPS do storage dos standbys e decidir se o ganho de catch-up do recovery_prefetch vale o risco de um travamento de horas na próxima rajada de escrita combinada com uma reconexão lenta de rede.
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.
Domain, enum, check ou tabela de referência: como escolher o guardião certo da integridade no PostgreSQL
Um post no Planet PostgreSQL mostra por que uma simples coluna de região expôs o limite de confiar só na aplicação para garantir integridade de dados no PostgreSQL.











