Disaster recovery: RTO, RPO e planos de contingência

1. Fundamentos de Disaster Recovery para Bancos de Dados

1.1 Definição de RTO e RPO

RTO (Recovery Time Objective) é o tempo máximo aceitável para restaurar a operação do banco de dados após um desastre. RPO (Recovery Point Objective) define a quantidade máxima de dados que pode ser perdida, medida em tempo. Para um banco SQL transacional, um RTO de 5 minutos significa que o sistema deve estar online em até 300 segundos; um RPO de 1 minuto implica que no máximo 60 segundos de transações podem ser perdidos.

1.2 Desastre lógico vs. físico

Desastres lógicos incluem erro humano (DROP TABLE acidental), corrupção de índices ou falhas em migrações. Desastres físicos abrangem falha de disco, queda de energia ou incêndio. A estratégia de recuperação difere: desastres lógicos geralmente exigem PITR (Point-In-Time Recovery), enquanto desastres físicos demandam replicação geográfica e failover.

1.3 Impacto em sistemas transacionais

Em um e-commerce, cada minuto de inatividade pode custar milhares de reais em vendas perdidas. A perda de 5 minutos de dados de pagamento pode gerar inconsistências contábeis e multas regulatórias. Por isso, RTO e RPO devem ser definidos por prioridade de dados.

2. Estratégias de Backup e Restauração com SQL

2.1 Backup lógico vs. físico

Backup lógico (pg_dump, mysqldump) exporta comandos SQL para recriar o banco. Backup físico (snapshots de sistema de arquivos, pg_basebackup) copia os arquivos de dados diretamente. Para RPO agressivos, backups físicos são superiores, pois permitem recuperação contínua via WAL.

# Backup lógico (PostgreSQL)
pg_dump -h localhost -U admin -Fc banco_ecommerce > backup_$(date +%Y%m%d).dump

# Backup físico (snapshot LVM)
lvcreate -L 10G -s -n snapshot_db /dev/vg01/db
mount /dev/vg01/snapshot_db /mnt/snapshot
rsync -av /mnt/snapshot/ /backup/fisico/

2.2 Backup completo, incremental e diferencial

Backup completo captura todo o banco. Backup incremental salva apenas mudanças desde o último backup (qualquer tipo). Backup diferencial salva mudanças desde o último backup completo.

# PostgreSQL: backup completo com pg_basebackup
pg_basebackup -h localhost -D /backup/completo -X stream -P

# Backup incremental via WAL archive
archive_command = 'cp %p /archive/%f'
# Configurar no postgresql.conf

# Script de backup diferencial (MySQL)
mysqldump --single-transaction --flush-logs \
  --master-data=2 banco_ecommerce > diferencial_$(date +%Y%m%d).sql

2.3 Testes de restauração

Validar backups é tão crítico quanto criá-los. Use ferramentas de verificação de checksum.

# Verificar integridade do backup (PostgreSQL)
pg_verify_checksums -d /var/lib/postgresql/data

# Script de teste de restauração
pg_restore -d banco_teste backup_completo.dump
psql -d banco_teste -c "SELECT count(*) FROM pedidos;"
psql -d banco_teste -c "SELECT count(*) FROM pagamentos;"

3. Replicação e Alta Disponibilidade

3.1 Replicação síncrona vs. assíncrona

Replicação síncrona garante RPO zero (nenhuma transação perdida), mas aumenta a latência. Replicação assíncrona oferece desempenho superior, com RPO de segundos a minutos.

# Configuração de replicação síncrona (postgresql.conf)
synchronous_commit = on
synchronous_standby_names = 'standby1'

# Verificar status da replicação
SELECT application_name, state, sync_state, write_lag
FROM pg_stat_replication;

3.2 Configuração com failover automático

Patroni e Repmgr gerenciam failover automaticamente, promovendo um standby quando o primário falha.

# Inicializar standby com pg_basebackup
pg_basebackup -h primario -D /var/lib/postgresql/data -X stream -P

# Configurar Patroni (exemplo YAML)
scope: ecommerce
consul:
  host: localhost
postgresql:
  parameters:
    wal_level: replica
    max_wal_senders: 5

3.3 Split-brain e resolução

Split-brain ocorre quando dois nós acreditam ser primários. Use pg_rewind para sincronizar o nó antigo.

# Resolver split-brain com pg_rewind
pg_rewind --target-pgdata /var/lib/postgresql/data \
  --source-server='host=novo_primario port=5432 user=replicator' \
  --dry-run

4. Planos de Contingência Baseados em SQL

4.1 Documentação do plano

Defina RTO e RPO por tabela. Tabelas críticas (pedidos, pagamentos) exigem RTO de 5 minutos; tabelas históricas (logs, auditoria) podem tolerar 1 hora.

-- Priorização de tabelas
CREATE TABLE plano_contingencia (
  tabela text PRIMARY KEY,
  rto_minutos int,
  rpo_minutos int,
  estrategia text
);

INSERT INTO plano_contingencia VALUES
  ('pedidos', 5, 1, 'replicacao_sincrona + pitr'),
  ('pagamentos', 5, 1, 'replicacao_sincrona + pitr'),
  ('logs_auditoria', 60, 60, 'backup_diario');

4.2 Scripts de contingência automatizados

Automatize failover e PITR com scripts shell.

#!/bin/bash
# Script de failover automático
PRIMARY_CHECK=$(psql -h primario -U admin -c "SELECT 1" 2>&1)
if [ $? -ne 0 ]; then
  echo "Primário falhou. Promovendo standby..."
  pg_ctl promote -D /var/lib/postgresql/data
  echo "Standby promovido a primário em $(date)"
fi

# Script de PITR
pg_restore -d banco_ecommerce --time=2025-01-15-14:30:00 \
  /archive/backup_completo.dump

4.3 Procedimentos de rollback

Use SAVEPOINT para pontos de rollback em transações longas e ferramentas como Liquibase/Flyway para reversão de migrações.

-- Rollback com SAVEPOINT
BEGIN;
SAVEPOINT antes_migracao;
UPDATE pedidos SET status = 'cancelado' WHERE data < '2024-01-01';
-- Se algo der errado:
ROLLBACK TO SAVEPOINT antes_migracao;
COMMIT;

-- Reverter migração com Flyway
flyway undo -url=jdbc:postgresql://localhost/banco_ecommerce

5. Testes de Recuperação e Simulação de Desastres

5.1 Criação de cenários de teste

Simule falhas reais para validar o plano.

# Cenário 1: Exclusão acidental de tabela
DROP TABLE pedidos CASCADE;

# Cenário 2: Corrupção de índice
REINDEX INDEX idx_pedidos_data;

# Cenário 3: Falha de disco simulada
systemctl stop postgresql
rm -rf /var/lib/postgresql/data/base/*

5.2 Execução de PITR

Recupere até um timestamp específico usando WAL archive.

# Configurar recovery.conf para PITR
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2025-01-15 14:30:00 BRT'
recovery_target_action = 'promote'

# Iniciar recuperação
pg_ctl start -D /var/lib/postgresql/data

5.3 Métricas de validação

Meça o RTO real com consultas de monitoramento.

-- Verificar tempo de recuperação
SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(),
       pg_last_wal_replay_lsn(), now() - pg_postmaster_start_time() as uptime;

-- Consultar logs de recuperação
SELECT * FROM pg_stat_activity WHERE query LIKE '%recovery%';

6. Monitoramento e Alertas para Prevenção

6.1 Alertas baseados em SQL

Detecte problemas antes que se tornem desastres.

-- Alerta de replicação atrasada
SELECT application_name, write_lag > interval '10 seconds' as alerta
FROM pg_stat_replication;

-- Alerta de espaço em disco
SELECT pg_database_size('banco_ecommerce') > 900 * 1024 * 1024 as alerta;

-- Alerta de fragmentação
SELECT schemaname, tablename, n_dead_tup > n_live_tup * 0.1 as alerta
FROM pg_stat_user_tables;

6.2 Automação com cron e NOTIFY

# Cron job para backup
0 2 * * * pg_dump -Fc banco_ecommerce > /backup/diario.dump

# Notificação via LISTEN/NOTIFY
CREATE OR REPLACE FUNCTION notificar_backup()
RETURNS event_trigger AS $$
BEGIN
  PERFORM pg_notify('backup_channel', 'Backup concluído em ' || now());
END;
$$ LANGUAGE plpgsql;

6.3 Auditoria de mudanças

Rastreie alterações críticas com triggers.

CREATE TABLE log_alteracoes (
  id serial PRIMARY KEY,
  tabela text,
  operacao text,
  dados_antigos jsonb,
  dados_novos jsonb,
  usuario text,
  data_hora timestamptz DEFAULT now()
);

CREATE OR REPLACE FUNCTION auditar_pedidos()
RETURNS trigger AS $$
BEGIN
  INSERT INTO log_alteracoes VALUES
    (DEFAULT, 'pedidos', TG_OP,
     row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb,
     current_user, now());
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_auditar_pedidos
AFTER INSERT OR UPDATE OR DELETE ON pedidos
FOR EACH ROW EXECUTE FUNCTION auditar_pedidos();

7. Caso Prático: Plano de Contingência para E-commerce

7.1 Definição de RTO e RPO

Para um e-commerce com 10.000 pedidos/dia:
- Tabelas pedidos e pagamentos: RTO 5 min, RPO 1 min
- Tabelas clientes e produtos: RTO 15 min, RPO 5 min
- Tabelas logs: RTO 1 hora, RPO 1 hora

7.2 Implementação

Combine replicação síncrona com backup contínuo de WAL.

# postgresql.conf
wal_level = replica
max_wal_senders = 5
wal_keep_size = 1024
archive_mode = on
archive_command = 'rsync -av %p usuario@backup:/archive/%f'
synchronous_commit = on
synchronous_standby_names = 'standby_ecommerce'

7.3 Script de failover e validação

#!/bin/bash
# failover.sh - Failover automático para e-commerce

PRIMARY_IP="192.168.1.10"
STANDBY_IP="192.168.1.20"

# Testar conectividade com primário
if ! psql -h $PRIMARY_IP -U admin -c "SELECT 1" &>/dev/null; then
  echo "Primário falhou! Iniciando failover..."

  # Promover standby
  ssh admin@$STANDBY_IP "pg_ctl promote -D /var/lib/postgresql/data"

  # Atualizar DNS ou balanceador
  ssh admin@balanceador "echo '$STANDBY_IP banco.ecommerce.com' >> /etc/hosts"

  # Validar integridade
  ssh admin@$STANDBY_IP "psql -U admin -c 'SELECT count(*) FROM pedidos;'"
  ssh admin@$STANDBY_IP "psql -U admin -c 'SELECT count(*) FROM pagamentos;'"

  echo "Failover concluído em $(date)" | mail -s "Failover" admin@ecommerce.com
fi

Referências