Pular para conteúdo

Recuperação de Desastres

Duas noites ruins, dois desastres muito diferentes.

Na primeira, a região de nuvem onde o BoxOffice roda sofre uma grande indisponibilidade: a rede dentro da região fica degradada por horas. Todas as réplicas de todos os serviços estão nessa região — três zonas de disponibilidade, todas afetadas. A redundância dentro da região não ajuda.

Na segunda, tudo está no ar. Às 22:00, um engenheiro roda um script de limpeza contra o que ele acha que é o banco de homologação. É o de produção: DELETE FROM orders WHERE created_at < now() — todos os pedidos somem. O standby do PostgreSQL em outra zona replica fielmente o delete em milissegundos. A redundância também não ajuda aqui; ela espalhou o estrago.

Recuperação de desastres (DR, disaster recovery) é o conjunto de estratégias, ferramentas e procedimentos para restaurar serviço e dados depois de eventos como esses — eventos maiores do que os padrões de resiliência da página anterior absorvem. Ela começa com dois números.


RPO e RTO

RPO — Recovery Point Objective : A quantidade máxima de dados, medida em tempo, que você aceita perder. RPO = 5 minutos significa que, depois da recuperação, no máximo os últimos 5 minutos de escritas podem faltar.

RTO — Recovery Time Objective : O tempo máximo desde o desastre até o serviço ser restaurado. RTO = 1 hora significa que os fãs podem voltar a comprar ingressos no máximo uma hora depois da falha.

flowchart LR
    lb["último ponto de<br/>recuperação bom"] -->|"RPO: dados perdidos"| d(("desastre<br/>22:00"))
    d -->|"RTO: serviço fora"| r["serviço<br/>restaurado"]

São decisões de negócio expressas em minutos, e se traduzem em dinheiro: cada minuto de RTO durante uma abertura de vendas é venda perdida; cada minuto de RPO são pedidos (e pagamentos!) que precisam ser reconstruídos à mão ou estornados. Números menores custam mais, de forma aproximadamente exponencial.

BoxOffice: RPO e RTO por serviço

Serviço / dado Por quê RPO RTO
order + livro-razão do payment Dinheiro; os fãs foram cobrados ≈ 0 para pagamentos confirmados (reconciliar com o provedor) 15 min
inventory (lugares, reservas) Nada de venda duplicada depois da recuperação ≈ segundos; reservas podem se perder, lugares vendidos não 15 min
catalog (eventos, locais) Mudanças são raras e podem ser refeitas 1 h 1 h (servido desatualizado pela CDN enquanto isso)
Índice do search Reconstruído a partir do catálogo n/a (dado derivado) 4 h
Analytics / warehouse Não é voltado ao cliente 24 h 2 dias

Dados diferentes merecem metas diferentes. Dar a meta mais rígida a tudo é o plano mais caro possível.

Pense antes: o standby replicou o DELETE. Em qual número a redundância ajudou?

Em nenhum. Um standby síncrono dá RPO e RTO excelentes para falhas de infraestrutura (a máquina do primário morre: promova o standby, sem perder nada). Para falhas lógicas — um script errado, um deploy com bug gravando lixo, um ransomware cifrando linhas — o standby é uma cópia exata do estrago. Recuperar exige uma cópia dos dados como estavam antes do erro: um backup, com recuperação para um ponto no tempo. Replicação e backups resolvem problemas diferentes; você precisa dos dois.


Backups

Um backup é uma cópia dos dados, mantida de forma independente do original, a partir da qual você pode restaurar. Os tipos:

Tipo O que copia Para restaurar precisa de Trade-off
Completo Tudo Só ele Lento e grande; feito raramente (por exemplo, semanal)
Incremental Mudanças desde o último backup de qualquer tipo Último completo + todos os incrementais desde então Pequeno e rápido de fazer; mais lento de restaurar
Diferencial Mudanças desde o último completo Último completo + último diferencial Meio-termo
Snapshot Imagem de um disco ou volume num instante (copy-on-write) O snapshot Rápido; consistente só como um crash, a menos que o banco seja avisado
Arquivamento contínuo (PITR) Um backup base + todo segmento do write-ahead log (WAL) Backup base + WALs reaplicados até o instante escolhido Restaura para qualquer momento, por exemplo 21:59:30

A recuperação para um ponto no tempo (PITR, point-in-time recovery) é o que salva a segunda noite. O PostgreSQL arquiva continuamente seu WAL3 em object storage (com ferramentas como pgBackRest ou WAL-G, ou automaticamente em bancos gerenciados como o Amazon RDS e o Cloud SQL). Para desfazer o delete das 22:00, restaure o backup base mais recente e reaplique o WAL até logo antes dele:

# restore the order database to 21:59:30, just before the accidental DELETE at 22:00
pgbackrest --stanza=order --type=time "--target=2027-03-14 21:59:30-03" \
  --target-action=promote restore

O RPO passa a ser o atraso do arquivamento do WAL (segundos a um minuto), e o RTO é o tempo de restauração, que você precisa estimar — e medir:

\[ t_{\text{restore}} \approx \frac{\text{base backup size}}{\text{restore throughput}} + \frac{\text{WAL to replay}}{\text{replay throughput}} \]

BoxOffice: quanto tempo leva a restauração?

O banco do order tem 2 TB. Restaurar de object storage a 200 MB/s leva \(2\,000\,000 / 200 = 10\,000\) s ≈ 2,8 horas, antes de reaplicar um dia de WAL. Isso estoura o RTO de 15 minutos. Opções: restaurar num banco já provisionado e próximo do alvo (backups base diários menores, armazenamento mais rápido), manter uma réplica atrasada (um standby que aplica o WAL com 1 hora de atraso, para que um erro possa ser pego antes de chegar a ela), ou dividir os dados para que a parte crítica (pedidos em aberto, o livro-razão de pagamentos) seja pequena. O número decide o projeto.

Regras para backups que funcionam de verdade

  • 3-2-1: pelo menos 3 cópias dos dados, em 2 tipos diferentes de armazenamento, 1 delas fora do local (outra região ou conta). A prática moderna acrescenta uma cópia imutável (object lock / WORM — escreva uma vez, leia muitas) que nem um atacante com credenciais de administrador consegue apagar, contra ransomware.
  • Separe o raio de impacto. Backups na mesma conta, com as mesmas credenciais da produção, podem ser apagados pelo mesmo erro ou pelo mesmo atacante.
  • Cifre os backups e gerencie as chaves separadamente; um backup é uma cópia completa dos dados dos seus clientes (a LGPD/GDPR se aplica).
  • Teste restaurações regularmente. Um backup que nunca foi restaurado é uma esperança. Automatize uma restauração periódica num ambiente descartável e verifique os dados (contagem de linhas, checksums, um smoke test da aplicação).
  • Monitore os jobs de backup e o atraso do arquivamento do WAL; alerte quando falharem ou atrasarem.

A lição do GitLab

Em janeiro de 2017, o GitLab.com perdeu cerca de seis horas de dados do banco de produção depois que um engenheiro apagou o diretório de dados no servidor errado durante um incidente. Dos vários mecanismos de backup e replicação existentes, nenhum pôde ser usado como previsto — alguns falhavam em silêncio havia muito tempo. A empresa publicou um postmortem franco4 que vale a leitura: backups que você não testa não são backups.


Multi-AZ e multi-região

Multi-AZ (uma região) Multi-região
Protege contra Perda de um data center / zona Perda de uma região inteira; algumas dependências regionais
Distância e latência Quilômetros, ~1 ms Centenas a milhares de km, dezenas de ms ou mais
Replicação Síncrona é viável (RPO ≈ 0) Em geral assíncrona (RPO = atraso da replicação)
Failover Automático, de segundos a minutos (bancos gerenciados fazem isso) Uma decisão maior: DNS, dados, capacidade; de minutos a horas
Custo Moderado Alto: uma segunda cópia de (parte de) tudo, mais transferência de dados
Complexidade Baixa Alta: consistência dos dados, deploys em duas regiões, testes

A replicação síncrona entre regiões coloca uma ida e volta entre regiões em cada escrita: \(\approx 30\)–\(100\) ms por commit costuma ser inaceitável no caminho de reserva de lugares. Por isso, DR multi-região em geral significa replicação assíncrona, e um RPO igual ao atraso da replicação no momento do desastre. É por isso que "RPO = 0 entre regiões" ou é muito caro (replicação síncrona, ou um banco globalmente consistente como Spanner ou CockroachDB pagando a latência) ou é um mal-entendido.

Multi-região também é uma questão jurídica: dados pessoais podem estar sujeitos a regras de residência (LGPD no Brasil, GDPR na União Europeia), que restringem quais regiões podem guardar a cópia.

Experimente no playground: RPO e RTO de um failover

O cenário db-failover é o checkout da venda relâmpago — 1.500 req/s, metade escritas — num primário com um seguidor replicado de forma assíncrona. Em t = 90 s o primário morre e a promoção leva 30 s. Aperte Run e leia dois números: cerca de 1.600 escritas perdidas (pedidos confirmados pelo primário mas ainda dentro do atraso de replicação: o RPO) e uma disponibilidade de cerca de 90,6% (30 s em que toda escrita falha: o RTO). Depois selecione o cluster e mude um parâmetro por vez:

  • Replicação: sync — as escritas perdidas caem para 0, a disponibilidade fica em 90,6%: um RPO melhor, o mesmo RTO, e toda escrita agora paga uma ida e volta ao seguidor.
  • Tempo de failover: 5 s (de volta ao async) — a disponibilidade sobe para cerca de 98,1%, mas os ~1.600 pedidos continuam perdidos.

A solução possível faz as duas coisas — replicação por quorum com 2 seguidores, failover de 5 s — e divide as chaves em 2 shards, para que um primário morto leve só metade das escritas: 0 perdidas, cerca de 99,2% de disponibilidade. RPO e RTO são parâmetros separados, e cada um tem seu preço.


As quatro estratégias de DR

O whitepaper de recuperação de desastres da AWS descreve um espectro de quatro estratégias, da barata e lenta à cara e instantânea1. As mesmas ideias valem em qualquer nuvem2.

Estratégia O que roda na região de recuperação antes do desastre RPO típico RTO típico Custo
Backup e restore Nada além dos backups (e da infraestrutura como código) Horas Horas a um dia $
Pilot light Dados replicados continuamente; infraestrutura central provisionada mas desligada ou no mínimo (sem servidores de aplicação) Minutos Dezenas de minutos a horas $$
Warm standby Uma cópia completa, em escala reduzida, rodando sem atender nada (ou tráfego de teste) Segundos a minutos Minutos $$$
Multi-site ativo-ativo Cópias completas em duas ou mais regiões, todas atendendo tráfego de produção ≈ 0 a segundos ≈ 0 (o tráfego muda de lugar) $$$$
flowchart LR
    subgraph A ["Região A (primária)"]
        appA["serviços"] --> dbA[("bancos")]
    end
    subgraph B ["Região B (recuperação)"]
        bk[("backups + WAL<br/>(object storage)")]
        iac["infraestrutura como código<br/>(nada rodando)"]
    end
    dbA -->|"backups, arquivo do WAL"| bk

Depois de um desastre, crie a infraestrutura a partir do código, restaure os bancos, implante os serviços, troque o DNS. O mais barato; o RTO é dominado pelo tempo de restauração e por quão ensaiado está o procedimento.

flowchart LR
    subgraph A ["Região A (primária)"]
        appA["serviços"] --> dbA[("bancos")]
    end
    subgraph B ["Região B (recuperação)"]
        dbB[("réplicas dos bancos<br/>(rodando)")]
        appB["serviços<br/>(desligados: 0 réplicas)"]
    end
    dbA -->|"replicação assíncrona"| dbB

A "chama piloto" são os dados, sempre ligados e atualizados. Num desastre, promova as réplicas, inicie os serviços (escalando a partir de 0), troque o DNS.

flowchart LR
    subgraph A ["Região A (primária, 100%)"]
        appA["serviços × 12"] --> dbA[("bancos")]
    end
    subgraph B ["Região B (standby, ~10%)"]
        appB["serviços × 2<br/>(rodando, ociosos)"] --> dbB[("réplicas")]
    end
    dbA -->|"replicação assíncrona"| dbB

Tudo já roda na região B em pequena escala e é testado continuamente. Num desastre, promova, escale para cima, mude o tráfego. Escalar não pode depender da região A (imagens, segredos, cotas disponíveis em B).

flowchart LR
    dns["GeoDNS / LB global"] --> A
    dns --> B
    subgraph A ["Região A (atendendo)"]
        appA["serviços"] --> dbA[("dados")]
    end
    subgraph B ["Região B (atendendo)"]
        appB["serviços"] --> dbB[("dados")]
    end
    dbA <-->|"replicação nos dois sentidos"| dbB

As duas regiões atendem usuários o tempo todo; perder uma significa mandar o tráfego dela para a outra (que precisa ter capacidade). A parte difícil são os dados: duas regiões aceitando escritas nos mesmos registros precisam de resolução de conflitos ou de um banco globalmente consistente.

Ativo-ativo e o inventário de lugares

Ativo-ativo é fácil para dados de leitura predominante: o catálogo pode ser lido em qualquer região a partir de réplicas locais e da CDN. É difícil para dados com invariantes fortes: se São Paulo e Virgínia aceitam reservas do lugar B-12 e replicam de forma assíncrona, dois fãs podem comprar o mesmo lugar — exatamente o que o BoxOffice nunca pode fazer (veja Consistência). As opções:

  • Região de origem por entidade: cada evento (show) pertence a uma região; todas as reservas e vendas dele vão para lá; a outra região é o warm standby dele. Fãs em outras regiões pagam um pouco de latência por aquele show. A posse só muda durante um failover.
  • Um banco globalmente consistente (Spanner, CockroachDB, YugabyteDB): consenso entre regiões em toda escrita, ao custo da latência entre regiões no caminho de reserva.
  • Multi-líder com resolução de conflitos (last-writer-wins, CRDTs): ótimo para um carrinho de compras ou uma lista de "favoritos", inaceitável para lugares.

Failover entre regiões

Um failover regional é uma sequência de passos, cada um com uma duração que se soma ao RTO:

sequenceDiagram
    participant M as monitores externos
    participant OC as plantão
    participant DNS as DNS (com health check)
    participant A as região A
    participant B as região B (warm standby)
    Note over A: região degradada às 22:00
    M->>OC: probes de compra falhando em 3 locais (2 min)
    OC->>OC: confirma que é regional, declara desastre (5 min)
    OC->>A: fencing: bloqueia escritas em A (se alcançável)
    OC->>B: promove as réplicas dos bancos (1 min)
    OC->>B: escala os serviços de 2 para 12 réplicas (5 min)
    OC->>DNS: aponta boxoffice.example para a região B
    Note over DNS: os clientes seguem depois do TTL (60 s)
    DNS-->>B: o tráfego chega, RTO ≈ 14 min
\[ \text{RTO} \approx t_{\text{detect}} + t_{\text{decide}} + t_{\text{promote}} + t_{\text{scale}} + t_{\text{DNS TTL}} \]

Pontos que decidem se funciona:

  • Detecção de fora. Probes que rodam dentro da região com falha podem falhar com ela. Use verificações sintéticas externas da jornada de compra.
  • Decisão automática ou humana? Failover automático é rápido mas arriscado: uma oscilação de rede que dispara um failover regional (e depois um failback) pode causar mais estrago do que evita (flapping). Muitos times automatizam dentro de uma região e mantêm uma decisão humana, com runbook ensaiado, entre regiões.
  • Fencing. Antes de o standby aceitar escritas, garanta que o primário antigo não consegue (revogue as credenciais, pare-o, ou use um lease). Dois primários aceitando reservas é split brain: lugares vendidos em dobro.
  • TTL do DNS. Clientes e resolvedores guardam as respostas de DNS pelo TTL; um TTL de 1 hora significa uma cauda de 1 hora de tráfego indo para a região morta. Use registros de DNS com health check (por exemplo, registros de failover do Amazon Route 53) e TTLs curtos no ponto de entrada (veja DNS e CDN).
  • Estabilidade estática. A região de recuperação não pode depender da que falhou: imagens de container, segredos, configuração e cotas de capacidade já precisam estar na região B.
  • Failback. Voltar é outra migração: ressincronize os dados de B para A e então troque num horário tranquilo. Planeje e ensaie isso também.

Experimente no playground

O cenário multi-region tem failover de DNS entre duas regiões. Rode com o perfil load e depois provoque uma indisponibilidade da região primária. Observe a taxa de erros até o health check do DNS fazer o failover, e anote quanto tempo leva. Mude o TTL do DNS e o intervalo do health check e meça o novo tempo de recuperação; depois reduza a capacidade da região standby e veja o que acontece com a latência logo após o failover.


O plano de DR do BoxOffice

Camada Serviços Estratégia RPO RTO
Camada 0 — receita order, payment, inventory Standby síncrono multi-AZ na região + warm standby numa segunda região (replicação assíncrona), backups com PITR e retenção de 35 dias, cópia imutável em outra conta Segundos (na região: 0) 15 min
Camada 1 — navegação catalog, gateway, auth, sala de espera Leituras ativo-ativo (CDN + réplicas nas duas regiões); escritas do catálogo só na primária 1 h para edições do catálogo 5 min (principalmente DNS)
Camada 2 — derivados search, notification Pilot light: reconstruir o índice a partir do catálogo; reenviar notificações pendentes a partir do Kafka Derivado / reprocessável 4 h
Camada 3 — interno analytics, warehouse Backup e restore 24 h 2 dias

Por que um warm standby para a camada 0, e não algo mais barato ou mais forte: o RTO de 15 minutos descarta backup e restore (só restaurar os 2 TB do banco do order leva cerca de 2,8 horas) e torna o pilot light arriscado (promover e escalar do zero leva dezenas de minutos); o ativo-ativo deixaria duas regiões venderem o assento B-12 ao mesmo tempo. O preço é uma segunda região rodando a cerca de um décimo da escala o ano todo, mais os exercícios que provam que ela consegue assumir.

E as práticas que mantêm o plano honesto:

  • Um runbook por cenário (perda de região, corrupção de banco, conta comprometida) com os comandos exatos, os responsáveis e os critérios de decisão.
  • Exercícios de DR duas vezes por ano: fazer de fato o failover para a região B por uma hora num período tranquilo, e restaurar um backup de produção num ambiente descartável todo mês. A página de engenharia do caos mostra como conduzi-los como game days.
  • Revisar RPO/RTO com o negócio todo ano: o contrato do estádio pode exigir mais, ou a camada de analytics pode aceitar menos.

Na entrevista

Quando perguntarem "e se a região cair?" ou "e se alguém apagar a tabela?", comece pelo RPO e RTO por classe de dados — e escolha a estratégia mais barata que os atende. Diferencie com clareza: replicação protege contra perda de infraestrutura, backups com PITR contra corrupção lógica. Para um sistema com invariantes fortes (lugares, saldos), explique por que ativo-ativo é difícil (escritas conflitantes) e proponha uma região de origem por entidade com warm standby. Apresente o RTO como uma soma de passos, incluindo o TTL do DNS.

Boas práticas

  • Combine RPO e RTO por classe de dados com o negócio e escreva-os no runbook — uma meta única e rígida para tudo é o plano mais caro; o livro-razão de pagamentos e o data warehouse de analytics diferem em um dia.
  • Cronometre todo teste de restore e compare com o RTO — a aritmética decide o projeto: 2 TB a 200 MB/s são 2,8 horas contra um RTO de 15 minutos.
  • Alerte sobre o atraso de arquivamento do WAL — ele é o RPO real da recuperação point-in-time; um atraso de uma hora é uma hora de pedidos a reconstruir à mão.
  • Mantenha o TTL de DNS do ponto de entrada em 60 s ou menos, com registros verificados por health check — cada segundo de TTL é uma cauda de fãs ainda mandados para a região morta.
  • Implante a região standby a partir do mesmo código de infraestrutura a cada release — imagens, segredos e configuração já na região B; um standby atualizado à mão se desvia até o dia em que é necessário.

Armadilhas comuns

  • "Temos réplicas, então temos backups." Réplicas copiam o erro.
  • Nunca testar uma restauração — ou medir o tempo dela só durante o desastre.
  • Backups na mesma conta e região da produção, apagáveis pelas mesmas credenciais.
  • Uma região de recuperação que depende da região que falhou (imagens, segredos, o sistema de CI, o plano de controle do provedor de DNS naquela região).
  • Ignorar a capacidade: um warm standby que não consegue escalar porque a região não tem cota livre num dia movimentado.
  • Sem fencing: o primário antigo volta e aceita escritas — split brain, lugares vendidos em dobro.

Como se conecta

flowchart LR
    BIZ["RPO e RTO<br/>por classe de dado"] --> STRAT["backup e restauração · pilot light ·<br/>warm standby · ativo-ativo"]
    INFRA["perda de infraestrutura<br/>(zona, região)"] --> REPL["replicação, multi-AZ<br/>→ Bancos de dados — replicação"]
    LOGIC["corrupção lógica<br/>(o DELETE errado)"] --> PITR["backups PITR, 3-2-1,<br/>cópia imutável"]
    REPL --> STRAT
    PITR --> STRAT
    STRAT --> AA["ativo-ativo vs a invariante dos assentos<br/>→ Distribuídos — consistência"]
    STRAT --> FO["failover regional:<br/>detectar, cercar, promover, escalar"]
    FO --> FENCE["fencing, sem split brain<br/>→ Distribuídos — consenso"]
    FO --> DNS["DNS com health check, TTL curto<br/>→ Borda — DNS"]
    FO --> DRILL["simulações de DR, testes de restauração<br/>→ Engenharia do caos"]

Componentes da paleta: sql (standby, réplicas, PITR) · objstore (backups base e o arquivo de WAL) · dns (registros de failover e TTL) · cdn (serve o catálogo desatualizado enquanto a origem se recupera) · kafka (reprocessa notificações não enviadas) · app (escalado de 2 para 12 réplicas na região standby). Cenários: db-failover, multi-region, db-replicas. Continua em: Bancos de dados — replicação, Distribuídos — consistência, Distribuídos — consenso, Borda — DNS e CDN, Cloud-native — entrega (infraestrutura como código), Projeto — game day.