01
O relatório que falha sozinho, só em horário comercial
O fechamento mensal agrega a tabela ledger_entries, com 310 milhões de lançamentos, e leva cerca de 11 minutos. Na primária, ele competia por cache e I/O com a API. Na réplica, deveria rodar em paz. O que o time viu foi um padrão estranho: o erro nunca aparecia nos primeiros 30 segundos, quase sempre aparecia antes de 1 minuto, e a taxa de falha acompanhava o volume de escrita da primária. Quanto mais lançamentos sendo conciliados durante o dia, mais o relatório morria.
O padrão tem explicação direta. Uma réplica em hot standby faz duas coisas ao mesmo tempo: atende consultas de leitura e aplica, em ordem, o WAL que a primária envia. Às vezes aplicar o próximo registro de WAL destruiria algo de que uma consulta em andamento ainda precisa. O Postgres chama isso de conflito de recuperação e resolve assim: o replay espera um prazo e, se a consulta não terminar, ela é cancelada.
PRIMARIA REPLICA (hot standby)
t=0 relatorio comeca, snapshot enxerga a linha versao A
t=20 s UPDATE troca A por B, commit
t=45 s autovacuum remove A (ninguem
na primaria precisa mais dela)
WAL: "limpeza, horizonte = xid de A" ---> replay recebe o registro de limpeza
o relatorio ainda pode precisar da versao A: CONFLITO
replay espera ate max_standby_streaming_delay (30 s)
t=75 s prazo esgotado: relatorio cancelado
ERROR: canceling statement due to conflict with recovery
replay aplica a limpeza e segue
Quem cede em cada configuracao:
padrao (30 s) a consulta e cancelada
hot_standby_feedback = on a primaria adia a limpeza (acumula tuplas mortas)
max_standby_streaming_delay=-1 o replay espera (a replica fica atrasada)Os 30 segundos do padrão são o valor padrão de max_standby_streaming_delay. A madrugada tranquila também se explica: sem UPDATE e DELETE na primária, não há versão de linha para limpar, então não há registro de limpeza no WAL e não há conflito. O relatório não tinha um problema de desempenho. Tinha um problema de convivência com o replay.