O backup que só existe no dia do restore
Estudos·

O backup que só existe no dia do restore

5 min de leitura← Voltar para timeline

O arquivo que ninguém nunca abriu

Eu tinha backup: um dump lógico por dia, gerado por um script que nunca falhou, num disco secundário. “Você tem backup?” — sim, e era verdade.

Só que dump lógico responde uma pergunta só: “perdi a tabela, me devolve ela”. Ele não responde “o deploy das 22h apagou uma linha que existia às 21h58, volta pro instante anterior”. Essa pergunta exige base física mais o WAL arquivado depois dela: recuperação ponto no tempo. Backup não ensaiado é hipótese.

Montar a cadeia e provar que anda

A configuração é curta. archive_mode = on só vale no start do servidor — esquecer o restart deixa setting could not be applied no pg_file_settings. O archive_command é dinâmico e precisa de guarda própria:

ALTER SYSTEM SET archive_command = 'test ! -f /var/lib/postgresql/wal-archive/%f && cp %p /var/lib/postgresql/wal-archive/%f';

O test ! -f ... && cp ... não é estilo. Um cp que falha em silêncio faz o Postgres achar que o segmento foi arquivado, reciclar o WAL e quebrar a cadeia no meio — e o erro só aparece no dia do restore. Com o test, a falha é visível no pg_stat_archiver no mesmo dia.

Prova medida agora: select archived_count, failed_count, last_archived_wal from pg_stat_archiver devolve 1 | 0 | 000000010000000000000020, às 03:02 UTC. Zero falhas, e o último segmento identificado por nome.

O ensaio é o produto

O ensaio de restore é onde tudo quebra. Um container efêmero extrai base.tar.gz e pg_wal.tar.gz num volume Docker, escreve restore_command e recovery.signal, sobe um Postgres descartável sem publicar porta e compara o inventário de tabelas por banco entre os dois lados.

Resultado de 19/10, literal:

producao: {"dogwalk": 31, "leve_lavanda": 16, "portifolio": 3, "yurumi": 1}
restore : {"dogwalk": 31, "leve_lavanda": 16, "portifolio": 3, "yurumi": 1}
WAL aplicado: sim

Rodei duas vezes na mesma madrugada (01:46 e 02:52): ensaio que passa uma vez pode ter passado por acidente.

As quatro falhas que o verde escondia

-X none. Sem stream, a base depende de o segmento do checkpoint já estar arquivado. Se ele ainda não fechou, o restore morre com FATAL: could not locate required checkpoint record — medi em quatro tentativas. Com -X stream a base leva o próprio pg_wal.tar.gz e fica autossuficiente.

Permissão de leitura. O tar extraía no host como uid 1000 e deixava os WAL em 600. O Postgres do container roda como uid 999 e não lia: mesmo FATAL, com o arquivo presente no disco. O chmod -R a+rX no destino virou parte do envio, não detalhe opcional.

Bind mount do home. O restore escreve como uid 999 e na rodada seguinte o host não conseguia nem apagar o diretório: tar: Função open falhou: Permissão negada. Tudo passou a rodar em volume Docker.

Número inventado. O relatório dizia que a base tinha 1.400.204 MB. Vinha de shutil.disk_usage(destino).used, que devolve o uso do disco, não do diretório. A base real tinha 20 MB — erro de seis dígitos que faz planejar retenção e capacidade em cima de ficção.

Teve também um bug de desempacotamento: uma função devolvia (lista, erro) e o retorno inteiro foi atribuído à lista. A contagem deu zero com doze segmentos no destino, e o script reprovou o próprio trabalho. Verificação que lê o objeto errado é pior que não verificar — produz falha convincente.

O que sobra fora da máquina

Base e WAL no mesmo disco respondem “corrompeu o arquivo”; não respondem “roubaram o computador”. O espelho subiu para um Drive, diário, com conferência por hash arquivo a arquivo — cp que “deu certo” não prova nada. Medido hoje: 3 bases, 16 segmentos, 96 GiB livres, último envio às 05:33. E o ensaio semanal baixa do Drive para restaurar: testar o disco local prova o disco, não o offsite.

Dois tropeços nessa camada. O cliente do Drive escreve NOTICE: no stderr em toda chamada, e concatenar stdout+stderr quebrava o json.loads — a falha aparecia como “Drive inacessível” com o Drive saudável. E o lock por idade deixou o envio travado seis horas depois de um processo morto; agora grava PID e trata /proc/<pid> inexistente como lock falso.

O que isso muda

Estado medido: três bases de 20 MB, 21 arquivos no WAL local (16 segmentos mais marcas .backup), 256 MB no total, e a retenção preserva as marcas — são o início da cadeia de cada base. Cadência: WAL a cada 30 minutos, base aos domingos, ensaio local semanal, espelho diário.

O que mudou não foi a infraestrutura, foi o critério: antes um backup, agora um restore que vi rodar duas vezes com inventário comparado nos dois lados.

Próximo passo

Falta a metade incômoda: testar a checagem, não o verde. Vou apontar o coletor para um destino inexistente e para uma pasta datada antiga, e confirmar que ele grita sem-base-backup e base-velha. Monitor que nunca acusou nada pode estar simplesmente cego.

Leia também

Mesmo projeto, mesma trilha — as histórias se conectam.

Próximo passo

Se isto te serviu, tem mais: escrevo sobre o mesmo assunto nos outros projetos, e a loja tem as ferramentas que eu uso aqui.

Sem spam, descadastro de um clique. Também estou aqui: Loja de skills · Portfólio