02 - Point-in-Time Recovery (PITR)
video: https://youtu.be/... no frontmatter da nota.Point-in-Time Recovery (PITR)
Point-in-Time Recovery é a capacidade de restaurar um banco de dados para um segundo específico do passado, combinando um backup de base com a aplicação sequencial do registro de transações gerado a partir daquele backup. Em vez de aceitar a granularidade de horas de um snapshot, o PITR reduz o RPO para minutos e permite desfazer exatamente o incidente: um UPDATE sem WHERE executado às 14h07 é revertido restaurando o banco às 14h06, um segundo antes da catástrofe. Em produção, o PITR é a diferença entre perder um dia de negócio e perder cinco minutos de escritas, e é o mecanismo subjacente ao backup automatizado de serviços gerenciados como RDS, Cloud SQL e Azure Database. Seu custo não é o backup, mas a retenção contínua de logs de transação e a disciplina operacional de mantê-los arquivados, testados e recuperáveis. Dominar o ciclo de base backup, arquivamento de WAL e replay é competência central de quem opera bancos críticos.
Conceito e problema que resolve
Todo banco de dados transacional mantém um log de transações append-only: o WAL no PostgreSQL, o redo log e o binlog no MySQL, o transaction log no SQL Server. Esse log registra cada mudança física e lógica necessária para reconstruir o estado do banco, e é a matéria-prima do PITR.
O problema que o PITR resolve é a granularidade. Snapshots e dumps capturam instantes discretos, e o espaço entre eles é área de perda: qualquer escrita que aconteceu depois do último backup some no desastre. Com o log de transações arquivado continuamente, essa área se reduz ao intervalo entre a última transação commitada e o instante alvo, tipicamente segundos.
O segundo problema é o erro lógico. Um snapshot restaura o banco exatamente como ele estava, inclusive o erro. O PITR permite parar o replay um segundo antes da transação destrutiva, descartando apenas o incidente e preservando tudo o que aconteceu antes dele.
A janela de recuperação é limitada pela retenção: recuperar para ontem exige que o log de ontem esteja arquivado; recuperar para 40 dias atrás exige 40 dias de logs contínuos e um backup de base anterior ao instante alvo. O tamanho da janela é uma decisão de custo explícita, não um detalhe de configuração.
Vale distinguir o PITR físico do PITR lógico. O físico, tratado aqui, reaplica registro de transações sobre uma cópia física do diretório de dados, e é o mecanismo dos bancos gerenciados. O lógico, disponível em ferramentas de binlog do MySQL ou em replay de streams de eventos, reaplica declarações SQL capturadas, é mais flexível para filtrar transações específicas, porém mais lento e sensível a diferenças de schema e de contexto. Em produção, o físico é o padrão; o lógico é ferramenta de cirurgia em incidentes pontuais.
Funcionamento interno: base backup mais replay de WAL
O ciclo do PITR tem três peças: um backup de base físico (ou snapshot) que serve de ponto de partida, o arquivamento contínuo dos segmentos de log gerados a partir daquele backup e um processo de replay que reaplica esses segmentos em ordem até o instante alvo.
O PostgreSQL documenta esse ciclo com precisão. Em modo contínuo de arquivamento, cada segmento de WAL de 16 MB é copiado para um repositório remoto assim que é fechado, via archive_command ou archive_library. Na recuperação, o servidor parte do backup de base, baixa os segmentos arquivados e os reaplica, parando no recovery_target_time, no recovery_target_xid ou no recovery_target_lsn configurado.
O replay é idempotente em relação ao estado físico do backup: aplicá-lo duas vezes sobre o mesmo ponto de partida produz o mesmo resultado. Por isso a prática padrão é restaurar o backup de base em um diretório novo e reproduzir o log nele, sem nunca sobrescrever os dados originais até confirmar que a recuperação funcionou.
A granularidade final é o próprio limite do log. O WAL registra transações completas no momento do commit, então o PITR restaura o estado visível em um instante, transação por transação. Não existe recuperação para o meio de uma transação não commitada, e transações longas podem adiar o checkpoint de consistência durante o replay.
-- PostgreSQL: ciclo básico de PITR
-- 1. Habilita o arquivamento contínuo de WAL
ALTER SYSTEM SET archive_mode = 'on';
ALTER SYSTEM SET archive_command =
'aws s3 cp %p s3://meu-bucket-wal/%f'; -- cada segmento fechado vai para o S3
ALTER SYSTEM SET wal_level = 'replica'; -- necessário para backup físico
-- 2. Tira o backup de base (com pg_basebackup, guarda o label gerado)
-- pg_basebackup -D /backup/base --wal-method=stream --checkpoint=fast
-- 3. No dia do desastre: restaura a base, cria o sinal de recuperação
-- e define o alvo, um segundo antes do incidente
-- Em restore_conf: restore_command = 'aws s3 cp s3://meu-bucket-wal/%f %p'
-- recovery_target_time = '2024-05-10 14:06:30-03'
-- recovery_target_action = 'pause' -- pausa o replay para validação
Janela de retenção e custo contínuo
A retenção de logs contínuos é o custo estrutural do PITR, e ele cobra todos os dias, não apenas no desastre. Cada segmento arquivado ocupa espaço no repositório pelo período inteiro da janela configurada, e o volume gerado cresce com a taxa de escritas do banco, não com o tamanho dos dados.
Um banco de 200 GB pode gerar 5 GB ou 500 GB de WAL por dia, dependendo da intensidade de escritas e de operations como vacuum e index rebuild. Dimensionar a janela sem medir o volume real de WAL é a receita para faturas surpresa ou para janelas truncadas sem aviso.
A prática de dimensionamento mede três números: taxa média e de pico de geração de WAL por hora, tamanho do backup de base e janela de retenção desejada. O custo total é aproximadamente o backup de base mais a soma do WAL da janela, com compressão aplicada quando o repositório suporta.
| Janela configurada | Custo relativo de storage | RPO efetivo | Capacidade de desfazer erro lógico |
|---|---|---|---|
| 24 horas | Baixa | Minutos | Último dia de operação |
| 7 dias | Moderada | Minutos | Uma semana de histórico |
| 35 dias | Alta | Minutos | Padrão de serviços gerenciados |
| 90 dias ou mais | Muito alta, considerar comprimir | Minutos | Compliance e casos legais |
A camada de storage do repositório também é decisão arquitetural: logs antigos raramente são acessados, e transição automática para armazenamento mais barato (classe de acesso infrequente) corta o custo sem mudar o contrato de recuperação.
Granularidade e escolha do alvo
O alvo de recuperação pode ser expresso de três formas, cada uma com um caso de uso dominante. Escolher o alvo correto no meio do incidente exige que essas opções já estejam ensaiadas.
- Timestamp (recovery_target_time): recupera para um instante de relógio, útil quando se sabe a hora aproximada do incidente; cuidado com fusos e com relógios de servidor dessincronizados.
- Transação (recovery_target_xid ou nome de transação): recupera até uma transação específica, útil quando o log de auditoria identificou a operação destrutiva exata.
- Posição no log (recovery_target_lsn ou nome de posição no binlog): recupera para uma marca física, a forma mais precisa e a usada por ferramentas de replay assistido.
A prática de incidente recupera primeiro em modo pause (o replay para no alvo e o banco fica em leitura somente leitura ou espera decisão), valida o estado restaurado contra a expectativa do negócio e só então promove a instância a primária. recovery_target_inclusive controla se a transação do alvo entra ou não no resultado.
Exercício de recuperação: o runbook mínimo
PITR não configurado para teste é teoria. O exercício a seguir é o roteiro que valida a capacidade real, e executá-lo periodicamente em ambiente isolado é o que transforma arquivos arquivados em garantia de recuperação.
# Runbook de teste de PITR em PostgreSQL, ambiente isolado
# 1. Provisiona volume novo e descompacta o backup de base escolhido
pg_ctl -D /restore/pgdata stop 2>/dev/null || true
rm -rf /restore/pgdata && mkdir -p /restore/pgdata
tar -xzf /backup/base/base_20240510_0400.tar.gz -C /restore/pgdata
# 2. Limpa artefatos que não devem ser reaproveitados da base
rm -f /restore/pgdata/postmaster.pid /restore/pgdata/postmaster.opts
# 3. Configura a recuperação: de onde buscar WAL e onde parar
cat > /restore/pgdata/restore_conf <<'EOF'
restore_command = 'aws s3 cp s3://meu-bucket-wal/%f %p'
recovery_target_time = '2024-05-10 14:06:30-03'
recovery_target_action = 'pause'
EOF
# 4. Inicia o servidor e observa o log até o replay pausar no alvo
pg_ctl -D /restore/pgdata start
tail -f /restore/pgdata/log/postgresql.log # aguarda "pausado no alvo"
# 5. Valida dados críticos, promove e redireciona a aplicação
SELECT count(*), max(updated_at) FROM pedidos; -- validação de negócio
SELECT pg_wal_replay_resume(); -- retoma e conclui
SELECT pg_promote(); -- torna-se primária
Cada execução do exercício deve registrar o tempo total do início ao primário em pé, porque esse número, e não a suposição de arquiteto, é o RTO real do PITR. Replay de horas de WAL acumulado é o componente dominante do tempo, e bases com alto volume de escritas podem exigir paralelismo ou alvos mais recentes para caber no RTO.
Comparações e posição na estratégia de backup
O PITR não substitui as outras camadas, ele as completa. A tabela abaixo posiciona cada mecanismo pela granularidade e pelo custo, e a combinação de produção típica usa os três com papéis distintos.
| Mecanismo | Granularidade de recuperação | Custo contínuo | Tempo de recuperação típico |
|---|---|---|---|
| Snapshot | Instante discreto, horas entre capturas | Baixo, incremental | Minutos a horas |
| Dump lógico | Instante discreto, granular por tabela | Médio, proporcional aos dados | Horas em bases grandes |
| PITR | Segundo específico dentro da janela | Alto, WAL contínuo + base | Base + replay, horas possíveis |
A divisão clássica de papéis: o snapshot diário é a âncora de cadeia que limita o volume de replay; o PITR é a camada de granularidade fina para erros lógicos e para o RPO de minutos; o dump lógico semanal é a apólice para corrupção de longo prazo e migrações de versão. O arquivo seguinte mostra como o RDS empacota exatamente essa combinação como serviço gerenciado.
Erros comuns e boas práticas
- Configurar archive_command sem monitorar se ele falha; um bucket cheio ou credencial expirada quebra o arquivamento em silêncio e só aparece no desastre, alertar sobre filas de arquivamento pendentes é obrigatório.
- Manter a janela de retenção no repositório, mas deletar backups de base antigos; sem base anterior ao instante alvo, o WAL daquele período é inútil, alinhar expirações de base e de log.
- Usar horário local em recovery_target_time sem registrar o fuso; documentar o alvo sempre em UTC e registrar o relógio do servidor nos runbooks.
- Testar apenas a captura e nunca a recuperação; o teste mensal de PITR em ambiente isolado é o único indicador honesto de RPO e RTO.
- Arquivar WAL no mesmo domínio de falha do banco; o repositório precisa estar em outra conta, região ou storage com política de imutabilidade, senão o desastre leva os dois.
- Deixar transações longas inflarem o WAL e os atrasos de arquivamento; monitorar lag de archiving e tamanho de transações como parte da observabilidade de backup.
- Ignorar a diferença entre RPO prometido e RPO real; medir o intervalo máximo observado entre transações commitadas e segmento arquivado, e reportá-lo junto com o SLO de backup.
- Promover a instância recuperada a primária sem revogar acessos que existiam no instante restaurado; se o incidente envolveu credencial comprometida, restaurar o banco para 14h06 restaura também o acesso do atacante, rodar rotação de credenciais como passo do runbook.