Arquitetura de Sistemas · Insper

Confiabilidade e Resiliência

Falhar de forma controlada, recuperar rápido, limitar o raio de impacto

10:02 na abertura de vendas do estádio

O provedor de pagamento vai de 200 ms para 4 s. Nada no BoxOffice está quebrado.
Por que fãs que só querem navegar no catálogo recebem erros um minuto depois?

  • O order espera o pagamento sem prazo → suas threads enchem.
  • O gateway espera o order → suas threads também enchem.
  • Toda rota compartilha essas threads → a lentidão se espalha para funcionalidades saudáveis.

Disponibilidade: duas alavancas

Termo Significado
MTTF tempo médio até a falha — quanto tempo funciona
MTTR tempo médio de reparo — detectar + diagnosticar + corrigir

Disponibilidade = MTTF / (MTTF + MTTR)

  • inventory: MTTF 720 h, MTTR 2 h → 99,72%.
  • Metade das falhas → 99,86%. Reinício automático em 1 min → 99,998%.

Recuperar mais rápido costuma ser a alavanca mais barata.

Os noves

Disponibilidade Por mês Por 28 dias Postura operacional
99,9% 43,8 min 40,3 min uma pessoa consegue fazer rollback
99,95% 21,9 min 20,2 min alertas rápidos, runbooks ensaiados
99,99% 4,38 min 4,03 min detecção e failover automatizados
99,999% 26,3 s 24,2 s automação em segundos; o risco são os deploys

Cada nove é 10× menos indisponibilidade — e sempre diga a janela (e se é tempo ou requisições).

Disponibilidade composta

Em série

A = A₁ × A₂ × … × Aₙ

Três serviços com 99,9% → 99,7%. Cada dependência síncrona baixa o teto.

Em paralelo

A = 1 − (1 − a)ⁿ

Duas réplicas com 99% → 99,99% — se as falhas forem independentes.

Caminho de compra do BoxOffice (ilustrativo): 1 instância de cada ≈ 98,3% (745 min/mês) → 3 réplicas ≈ 99,79% → + failover do banco ≈ 99,84% → + segundo provedor de pagamento ≈ 99,94% (26 min/mês). Cada passo expõe o próximo elo mais fraco.

Redundância e failover

Padrão Tempo de failover Exemplo no BoxOffice
Ativo-ativo segundos (health checks) réplicas sem estado de gateway, order, catalog
Ativo-passivo (hot standby) segundos a minutos PostgreSQL primário + standby em outra AZ
Cold standby minutos a horas workers de analytics recriados a partir de imagens
  • N + 1: depois de perder uma réplica (ou uma AZ de três), o resto aguenta o pico.
  • Sem estado faz failover de forma trivial; estado exige replicação, fencing, nada de split brain.
  • AZs ficam a ~1 ms (proteção barata); regiões ficam a dezenas de ms (DR).

Como sistemas distribuídos falham

Classe Tratamento
Crash (fail-stop) health checks, reinício, failover
Transitória retry com backoff e jitter
Lenta / gray failure timeouts, breakers que contam chamadas lentas, SLIs de latência
Bizantina / incorreta checksums, validação, reconciliação
Correlacionada rollouts graduais, multi-zona, testes de caos

O componente mais perigoso não é o morto — é o lento. Quem chama não distingue lento de morto, então, sem prazo, espera.

Falha em cascata, em números

chamadas em voo = taxa de chegada × tempo por chamada (Lei de Little)

Latência do pagamento Em voo a 100/s Pool de 200 do order
200 ms 20 10% ocupado
1 s 100 50% ocupado
4 s 400 esgotado → toda rota espera
  • Timeout limita o tempo por chamada · circuit breaker para de chamar · bulkhead limita as threads.

Playground: cascading-failure — adicione um timeout, depois um breaker, e observe as rotas saudáveis.

Falhas que se alimentam

  • Tempestades de retry: retries multiplicam a carga quando a dependência está mais fraca.
  • Manada na recuperação: todo mundo reconecta no mesmo instante.
  • Espiral dos health checks: instâncias sobrecarregadas são removidas, as restantes recebem mais carga.
  • Falha metaestável: o gatilho passou, mas o trabalho que ele criou (retries, timeouts, misses) mantém o sistema caído.

A saída é descartar carga: load shedding, retry budgets, circuit breakers.

Timeouts e orçamentos de prazo

Camada Orçamento
Gateway 6 s
Handler de compra do order 5 s
order → payment 2 s × 2 tentativas + backoff ≈ 4,3 s
payment → provedor 1,8 s
  • Perto do p99,9 normal da dependência; timeouts de conexão (300 ms) e de leitura (2 s).
  • Por dependência, encolhendo com a profundidade; propague o prazo (deadlines do gRPC).
  • Um timeout numa cobrança deixa um resultado desconhecido → chave de idempotência.

Retries: amplificação, jitter, orçamentos

  • Repita só falhas transitórias (erros de conexão, 503, 429); nunca 4xx ou "cartão recusado".
  • Uma camada: 3 camadas × 3 tentativas = 27 chamadas por clique.
  • Full jitter: wait = random(0, base · 2ⁿ) — 1.000 clientes que falham juntos se recuperam em ~1,5 s em vez de ondas por 15 s.
  • Retry budget (retries ≤ 10% do tráfego): sem ele, os retries podem manter um sistema caído de forma metaestável.

Playground: retry-storm — troque para backoff + jitter e adicione um retry budget.

Circuit breaker e fallback

Fechado → (≥ 50% de falhas ou chamadas lentas em 20) → Aberto (30 s) → Meio-aberto (3 testes) → Fechado / Aberto

  • Aberto = falhar em microssegundos: threads livres, a dependência ganha espaço para respirar.
  • Ignore erros de negócio: um cartão recusado é uma resposta correta.
  • Fallback = degradação graciosa: esconder recomendações, resultados de busca desatualizados…
  • Para o pagamento: nunca finja sucesso — "pagamento temporariamente indisponível, lugares reservados por 10 min".

Bulkheads e load shedding

Bulkhead

  • Um pool por dependência.
  • 100 chamadas/s × 0,2 s = 20 em voo → bulkhead de 50.
  • Pagamento a 4 s → no máximo 50 threads esperam, não 400.

Load shedding

  • Rejeite cedo e barato (503 + Retry-After).
  • Por prioridade: compra > navegação > recomendações.
  • Descarte trabalho velho; limites de concorrência adaptativos.

Rate limiting é por cliente (token bucket no gateway); load shedding protege o sistema todo. A sala de espera é load shedding feito com gentileza.

Resilience4j em order → payment

resilience4j:
  bulkhead.instances.payment:
    max-concurrent-calls: 50
  circuitbreaker.instances.payment:
    sliding-window-size: 20
    failure-rate-threshold: 50
    slow-call-duration-threshold: 1500ms
    wait-duration-in-open-state: 30s
  retry.instances.payment:
    max-attempts: 2
    wait-duration: 200ms
    enable-randomized-wait: true

Ordem padrão: Retry(CircuitBreaker(RateLimiter(TimeLimiter(Bulkhead(call))))) — toda tentativa é contada pelo breaker. Mais um Idempotency-Key em toda cobrança.

Health checks

Probe Pergunta Na falha
Startup terminou de subir? continua esperando (até um limite)
Liveness o processo travou? reinicia o container
Readiness pode atender tráfego agora? remove do load balancer

Nunca coloque dependências no liveness: uma oscilação de 20 segundos no banco reinicia todas as réplicas de uma vez.

Recuperação de desastres: RPO e RTO

  • RPO: quantos dados (em tempo) você aceita perder.
  • RTO: quanto tempo até o serviço voltar.
  • Defina por classe de dados: pagamentos ≈ 0 / 15 min · catálogo 1 h / 1 h · analytics 24 h / 2 dias.

Replicação não é backup: o standby replica DELETE FROM orders em milissegundos. Falhas lógicas exigem backups com recuperação para um ponto no tempo — e um tempo de restauração medido (2 TB a 200 MB/s ≈ 2,8 h).

Backups 3-2-1 + uma cópia imutável; conta separada; teste restaurações todo mês (lembre do GitLab, 2017).

As quatro estratégias de DR

Estratégia Na região de recuperação antes do desastre RPO RTO Custo
Backup e restore backups + infraestrutura como código horas horas–dia $
Pilot light dados replicados; serviços desligados minutos dezenas de min–horas $$
Warm standby cópia completa, reduzida, rodando segundos–min minutos $$$
Ativo-ativo cópias completas, todas atendendo ≈ 0 ≈ 0 $$$$

A replicação entre regiões costuma ser assíncrona: RPO = atraso da replicação. Residência de dados (LGPD/GDPR) restringe as regiões.

Failover regional

RTO ≈ detectar (2 min) + decidir (5) + promover o banco (1) + escalar (5) + TTL do DNS (1) ≈ 14 min

  • Detecte de fora da região; decida com um runbook ensaiado.
  • Faça fencing do primário antigo — dois primários = lugares vendidos em dobro.
  • Estabilidade estática: imagens, segredos, cotas já na região B.
  • Lugares não podem ser multi-líder: região de origem por evento + warm standby, ou um banco globalmente consistente.

Playground: multi-region — derrube a região primária e depois mude o TTL do DNS.

Engenharia do caos

Experimentos que dão confiança de que o sistema suporta condições turbulentas em produção.

  1. Estado estável como SLIs → 2. hipótese → 3. injetar uma falha real → 4. observar → 5. analisar → 6. melhorar.
  • BoxOffice: o Toxiproxy adiciona 3 s ao provedor sob carga k6 → espera-se o breaker aberto em < 30 s, catálogo estável.
  • Raio de impacto pequeno: Compose → homologação → um pod canary → mais amplo; aborte com burn rate > 10×.
  • Ferramentas: Toxiproxy, Chaos Mesh, LitmusChaos, AWS FIS, Azure Chaos Studio, Gremlin.

Game day: a flash sale

Horário Injeção Esperado
10:10 latência de 3 s no provedor breaker abre; lugares reservados
10:25 um pod do order morto por minuto N+1 absorve
10:40 broker Kafka parado outbox cresce e escoa; nenhum e-mail perdido
10:55 failover do banco ~30 s de erros de escrita
11:10 Redis esvaziado a proteção contra stampede se mantém

Papéis: mestre do jogo, plantão, observadores, escriba. Resultado: uma lista de melhorias sem culpados.

O mapa de conceitos de confiabilidade

Componente Pergunta que responde Conceitos-chave
lb, app perder uma réplica ou zona nos para? noves, série/paralelo, N+1, multi-AZ
thirdparty, payment e se uma dependência ficar lenta? timeout, retry + jitter, breaker, bulkhead
apigw o que recusamos em sobrecarga? rate limiting, descarte de carga, sala de espera
healthcheck o tráfego deve chegar a esta instância? liveness, readiness, startup
metrics, alerting somos confiáveis o suficiente? SLI, SLO, orçamento de erros, burn rate
sql, objstore, dns quantos dados e quanto tempo podemos perder? RPO/RTO, PITR, estratégias de DR, failover

A redundância mantém as instâncias servindo, os padrões contêm dependências lentas, o DR cobre o que a redundância não cobre, os SLOs dizem quanto basta — e o caos prova tudo isso.

Conclusões

  1. Disponibilidade = MTTF / (MTTF + MTTR); em série multiplica, em paralelo 1 − (1 − a)ⁿ.
  2. Dependências lentas causam cascatas por pools esgotados — a Lei de Little diz quão rápido.
  3. SLOs por jornada do usuário; o error budget e seu burn rate guiam deploys e alertas.
  4. Timeout · um retry com jitter + orçamento · breaker que conta chamadas lentas · bulkhead · shedding · fallback honesto.
  5. DR a partir de RPO/RTO: replicação ≠ backup; escolha a estratégia mais barata que os atende.
  6. Experimentos de caos e game days provam as defesas — observabilidade primeiro.

Roteiro: (1) disponibilidade, noves, disponibilidade composta, redundância; (2) modos de falha e cascatas; (3) SLIs, SLOs, error budgets; (4) padrões de resiliência; (5) recuperação de desastres; (6) engenharia do caos e game days.

Deixe a turma raciocinar. O resto da aula: medir quanta falha é aceitável, construir defesas, recuperar de desastres e provar que as defesas funcionam.