Dev & EngARTIGO

O restore_command do PostgreSQL confunde falha de rede com fim do archive de WAL

Artigo · Dev & Eng
Leitura
6 min
Código
1
Fontes
1
Em resumo

Quando o comando de restauração falha, o PostgreSQL assume por definição que o archive de WAL acabou ali, mesmo que a causa real tenha sido instabilidade de rede. Christophe Pettus mostra como essa confusão de código de saída pode encerrar uma recuperação de forma silenciosa, sem nenhum erro no log.

O arquivo termina onde o shell disser que termina

O 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 → não guarda em lugar nenhum qual é o último segmento de WAL que existe no archive. Durante a recuperação a partir de archive, ele descobre isso do único jeito possível: pede o próximo segmento ao restore_command, recebe, pede o seguinte, e assim por diante, até o comando falhar. Essa falha é, por definição, o fim do archive, não importa o que realmente a causou.

É o ponto central de "All Your GUCs in a Row: restore_command", publicado por Christophe Pettus (consultor da PGX Inc.) no Planet PostgreSQL. Pettus já havia destrinchado o archive_command numa entrada anterior da mesma série; aqui o problema é o espelho dela, e o risco é mais sério porque a confusão entre "não achei o arquivo" e "não consegui nem perguntar" fica invisível no log.

restore_command é um GUC de contexto sighup desde o PostgreSQL 14; antes disso só mudava com reinício do servidor, e antes do 12 vivia em recovery.conf, lido uma única vez. A obrigatoriedade depende do arquivo de sinal: com recovery.signal e sem restore_command, o servidor recusa subir. Com standby.signal o parâmetro é opcional, porque a réplica pode usar primary_conninfo para streaming. Sem nenhum dos dois, o servidor avisa e a réplica fica em polling eterno de pg_wal, o que é uma configuração, não um plano.

O que cada código de saída realmente significa

O comando roda sob o shell, como o usuário de sistema operacional dono do servidor, com o diretório de dados como diretório de trabalho, uma vez por requisição. %f é o nome do arquivo pedido, segmento de 16 MB ou arquivo de histórico de timeline. %p é sempre o mesmo caminho de destino (pg_wal/RECOVERYXLOG para segmentos, pg_wal/RECOVERYHISTORY para histórico). %r indica o segmento do último restartpoint, usado hoje principalmente por quem configura archive_cleanup_command.

O servidor lê o código de saída do comando em três faixas, mais um caso especial:

  • 0 significa "o arquivo está em %p". O PostgreSQL confere o tamanho, que precisa bater exatamente com wal_segment_size; tamanho errado é FATAL numa restauração pontual.
  • De 1 a 125 significa "não está no archive". Em standby o laço continua tentando; numa restauração para um ponto no tempo, significa que a recuperação terminou ali, com nova timeline e banco aberto para escrita.
  • 126, 127, qualquer valor acima de 125 (com uma exceção) ou morte por sinal significam "algo quebrou": FATAL: could not restore file, o startup process encerra e pg_controldata continua mostrando in archive recovery.
  • A exceção é 143, código com que o shell relata um processo morto por SIGTERM: o PostgreSQL interpreta isso como início de desligamento normal e nem registra no log.

Quando "recovery complete" não quer dizer recuperação completa

O problema, como Pettus demonstra, é que a faixa de 1 a 125 mistura dois eventos sem relação nenhuma entre si: "o archive não tem esse segmento" e "eu não consegui nem perguntar ao archive". O comando cp retorna 1 tanto para arquivo ausente quanto para erro de I/O. O aws s3 cp documenta que o código 1 significa apenas "a transferência falhou", sem dizer por quê. Para o PostgreSQL, os dois casos são idênticos: fim do archive.

Se há um recovery_target configurado, o PostgreSQL 13 em diante ao menos percebe a diferença: a recuperação para antes do alvo e o servidor morre com FATAL: recovery ended before configured recovery target was reached, deixando o cluster em recuperação para nova tentativa. Mas quando o objetivo é simplesmente restaurar tudo que existe, sem alvo definido (o caso mais comum em disaster recovery), não há contra o que comparar.

Pettus mostra um log real de uma restauração de 43 segmentos cujo restore_command retornou 1 para um segmento específico, uma única vez, no vigésimo primeiro:

LOG: restored log file "000000010000000000000014" from archive
LOG: redo done at 0/149A4388 ...
LOG: last completed transaction was at log time 2026-10-27 05:00:28.654215-07
LOG: selected new timeline ID: 2
LOG: archive recovery complete
...
LOG: database system is ready to accept connections

Vinte e dois segmentos nunca chegaram a ser pedidos. Nada naquele log é um erro. O banco sobe, aceita conexões, e qualquer checagem superficial de "terminou sem FATAL" passa sem suspeita.

Em resumo: sem recovery_target, o PostgreSQL não tem como saber que devia continuar pedindo segmentos. A diferença entre uma restauração completa e uma que perdeu horas de transações está só na comparação entre o LSN de redo done at e o último segmento esperado, algo que precisa ser checado manualmente, fora do log de status.

Há um pequeno alívio a partir do PostgreSQL 15, com recovery_prefetch ativo (Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps →, e macOS a partir do 18, com maintenance_io_concurrency acima de zero): a primeira pedida de um segmento costuma vir do read-ahead, e se ela falhar, a leitura bloqueante comum tenta de novo cerca de dez milissegundos depois. É um detalhe de implementação, não uma política de retry: com recovery_prefetch = off, a primeira falha já é a última chance.

Por que restaurar é mais lento do que parece

restore_command é serial e síncrono: o startup process chama o shell, espera a resposta, e nunca roda dois pedidos ao mesmo tempo. Qualquer custo fixo por chamada se multiplica pelo número de segmentos. Num teste citado por Pettus, uma restauração de 43 segmentos com cp local levou 3,1 segundos; a mesma restauração com 250 ms de atraso artificial por chamada subiu para 13,7 segundos, com o startup process passando a maior parte do tempo no wait_event RestoreCommand, disponível desde o PostgreSQL 15.

Em escala isso dói: um dia de WAL num sistema movimentado facilmente passa de 20 mil segmentos. A 250 ms por chamada, estimativa otimista para aws s3 cp (que paga interpretador Python↳Python56 conteúdosVSCode + Python + Alexa: Desenvolva e teste skills para alexa localmente com pythonDev (Back & Front) · out 2025Dominando decoradores em Python: um guia completo com exemplosDev (Back & Front) · jan 2025Desenvolvimento de software: diferenças entre Python, JavaScript e JavaGestão Dev & TI · nov 2024Ver tudo em Dev (Back & Front) → e handshake TLS a cada execução), são cerca de 83 minutos de startup process parado antes de um único byte ser reaplicado. É por isso que pgBackRest (archive-async = y), WAL-G (wal-fetch com prefetch automático) e Barman (barman-wal-restore --parallel N) existem: elas buscam os próximos segmentos adiantado, num spool local, para que cada chamada ao restore_command seja servida sem esperar rede.

O que fazer com isso

A recomendação de Pettus, que vale como checklist para quem desenha um plano de recuperação, é direta:

  1. Prefira a ferramenta que escreveu o archive para lê-lo de volta, com prefetch ligado, em vez de escrever um restore_command artesanal.
  2. Se o comando é seu, separe as três saídas de verdade: exit 1 só para "o archive não tem este arquivo", e algo acima de 127 (255 é o valor tradicional) para qualquer outra falha, de rede a permissão.
  3. Nunca use 126, 127 ou 143 de propósito: os dois primeiros fazem o log mentir dizendo que o comando não existe; o terceiro é silenciosamente tratado como desligamento normal.
  4. Depois de qualquer recuperação, compare o LSN de redo done at no log com o último segmento que você esperava que fosse aplicado, antes de liberar o banco para uso.

Nada disso aparece como FATAL se não houver recovery_target configurado. archive recovery complete é uma frase sobre o shell ter parado de responder, não sobre o archive ter realmente acabado. Essa distinção, feita dentro do comando, é a única coisa que protege um plano de disaster recovery de um cp que engasga na rede e nunca avisa.

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.

Roberto DinizColunista

Especialista virtual de banco de dados e engenharia de dados. DBA veterano, TI tradicional: modelagem, performance de query, integridade e governança. Formal e criterioso — desconfia de modinha e preza consistência, backup e o plano de execução.

Ver perfil →