Pular para conteúdo

Visão geral

São 10:02, dois minutos depois da abertura de vendas do estádio. O BoxOffice está aguentando: a sala de espera admite fãs em ritmo constante, o inventory está a 60% da sua capacidade. Então o provedor externo de pagamento começa a responder em 4 segundos em vez de 200 ms. Em um minuto, o order fica sem threads livres; o gateway, esperando pelo order, também; e fãs que só queriam navegar pelo catálogo recebem erros. Nada no BoxOffice está quebrado — nenhum bug, nenhum crash, nenhum disco cheio. O sistema simplesmente não tinha defesa contra uma dependência lenta, e a lentidão se espalhou até derrubar tudo.

Esta aula é sobre construir essas defesas e sobre decidir com números quanta falha é aceitável. Confiabilidade é a probabilidade de um sistema executar a função exigida sem falhar, em condições definidas, por um período definido1. Em sistemas distribuídos essa definição cobre muito mais que hardware: bugs de software, dependências sobrecarregadas, partições de rede, deploys ruins, data centers perdidos e erros de operação.

Um sistema confiável não é aquele que nunca falha. É aquele que falha de forma controlada, se recupera rápido e limita o raio de impacto (blast radius) de qualquer falha isolada — sendo o raio de impacto o conjunto de usuários e componentes afetados por uma falha.

A história até aqui

Capítulo 10 da história do curso. O BoxOffice espalha a multidão das 10:00 por réplicas stateless (aula 4, aula 5), vende cada assento uma única vez (aula 6), responde leituras de um cache (aula 7) e admite fãs por uma sala de espera (aula 8); a aula 9 o tornou visível, com um SLO por jornada e um trace por checkout. Agora o trace mostra o problema: o provedor de pagamento responde em segundos em vez de milissegundos, e nada no BoxOffice está quebrado. Esta aula responde: como impedir que uma dependência lenta derrube tudo, e quanta redundância o SLO nos pede para comprar?

Objetivos de aprendizagem

Ao final desta aula você deve ser capaz de:

  • Calcular a disponibilidade a partir de MTTF e MTTR, a indisponibilidade permitida por um número de "noves" e a disponibilidade de ponta a ponta de componentes em série e em paralelo.
  • Explicar como uma dependência lenta causa uma falha em cascata e estimar com a Lei de Little os recursos que ela esgota.
  • Escolher e configurar padrões de resiliência — timeouts, retries com backoff e jitter, circuit breakers, bulkheads, load shedding e rate limiting — para uma chamada entre dois serviços, e prever o que cada um faz sob falha.
  • Projetar uma estratégia de recuperação de desastres a partir de metas de RPO e RTO, escolhendo entre backup e restore, pilot light, warm standby e ativo-ativo.
  • Planejar um experimento de caos e um game day com uma hipótese de estado estável e um raio de impacto controlado.

Roteiro

  1. Esta visão geral — disponibilidade, MTTF/MTTR, os "noves", disponibilidade composta, redundância e failover, modos de falha e falhas em cascata.
  2. Padrões de resiliência — timeout, retry com backoff e jitter, circuit breaker, bulkhead, load shedding, rate limiting, health checks, com Resilience4j.
  3. Recuperação de desastres — RPO e RTO, backups, multi-AZ e multi-região, e as quatro estratégias de DR.
  4. Engenharia do caos — injetar falhas de propósito para provar que as defesas funcionam; game days.
  5. Quiz — teste seu entendimento.

Slides

Clique nos slides e use ← → para navegar; F abre em tela cheia e P abre a visão do apresentador, com as notas. Abrir os slides em uma nova aba


Disponibilidade

A face mais visível da confiabilidade é a disponibilidade: a fração do tempo (ou das requisições) em que o sistema funciona. Confiabilidade e disponibilidade são relacionadas, mas não são a mesma coisa. Um serviço que cai por um segundo a cada minuto falha com frequência (confiabilidade ruim), mas ainda tem cerca de 98% de disponibilidade; um serviço que falha uma vez por ano mas leva uma semana para ser reparado é muito confiável e pouco disponível. A disponibilidade vem de duas grandezas mensuráveis:

Termo Nome completo Significado
MTTF Mean Time To Failure Tempo médio de funcionamento antes de uma falha
MTTR Mean Time To Repair (ou Recover) Tempo médio para detectar, diagnosticar e restaurar o serviço depois de uma falha
MTBF Mean Time Between Failures MTTF + MTTR — o ciclo completo de falha e recuperação
\[ \text{Availability} = \frac{MTTF}{MTTF + MTTR} \]

BoxOffice: duas formas de melhorar o inventory

O inventory cai em média uma vez a cada 30 dias (MTTF = 720 h). A cada vez, leva 2 horas até alguém ser acionado, achar o problema e reiniciá-lo (MTTR = 2 h).

Disponibilidade = 720 / (720 + 2) ≈ 99,72%.

  • Reduzir as falhas pela metade (MTTF = 1440 h): 1440 / 1442 ≈ 99,86%.
  • Deixar o Kubernetes detectar o crash e reiniciar o pod em 1 minuto (MTTR ≈ 0,017 h): 720 / 720,017 ≈ 99,998%.

Recuperar mais rápido foi o ganho maior — e muito mais barato que nunca falhar.

O trabalho de confiabilidade puxa uma de duas alavancas. Aumentar o MTTF (menos falhas): testes, revisão de código, redundância, projeto tolerante a falhas, deploys cuidadosos. Reduzir o MTTR (recuperação mais rápida): bons alertas, dashboards e traces, runbooks, reinícios e failover automáticos, rollbacks rápidos. Em sistemas modernos o MTTR costuma ser a alavanca mais barata, e o próprio MTTR é uma soma — tempo para detectar + tempo para diagnosticar + tempo para corrigir — cada parcela atacada por uma prática diferente (veja Observabilidade).

Os "noves"

Metas de disponibilidade são expressas em número de "noves". A tabela mostra a indisponibilidade que cada meta permite (ano = 365,25 dias, mês = 1/12 de ano ≈ 30,44 dias):

Disponibilidade Por ano Por mês Por 28 dias Por semana
99% ("dois noves") 3,65 dias 7,31 horas 6,72 horas 1,68 hora
99,9% ("três noves") 8,77 horas 43,8 minutos 40,3 minutos 10,1 minutos
99,95% 4,38 horas 21,9 minutos 20,2 minutos 5,04 minutos
99,99% ("quatro noves") 52,6 minutos 4,38 minutos 4,03 minutos 1,01 minuto
99,999% ("cinco noves") 5,26 minutos 26,3 segundos 24,2 segundos 6,05 segundos

Cada nove a mais é 10× menos indisponibilidade, e não é uma melhoria incremental: muda a forma de operar. Com três noves, uma pessoa pode ser acionada, abrir o notebook e fazer rollback. Com quatro noves, o orçamento do mês inteiro é menor que o tempo de acordar; detecção e recuperação precisam ser automáticas. Com cinco noves, até o failover automático precisa terminar em segundos, e um único deploy ruim pode gastar o orçamento do ano.

Sempre diga a janela

"99,9%" sozinho é ambíguo: 43,8 minutos em um mês de calendário, 40,3 minutos em uma janela de 28 dias. Sempre que citar disponibilidade, diga em qual janela — e se ela conta tempo ou requisições (veja SLOs).


Disponibilidade composta

Uma requisição atravessa muitos componentes, e a disponibilidade que o usuário vê depende de como eles se combinam2. Duas regras cobrem quase todos os casos (veja também Matemática da disponibilidade).

Quando uma requisição precisa de todos os componentes, as disponibilidades se multiplicam:

\[ A_{\text{series}} = A_1 \times A_2 \times \cdots \times A_n \]

Três serviços com 99,9% cada dão \(0.999^3 \approx 99.7\%\). A cadeia é pior que seu elo mais fraco, e cada dependência síncrona adicionada baixa o teto. Um serviço nunca pode prometer mais disponibilidade que o produto das dependências que ele chama de forma síncrona.

Quando qualquer uma de \(n\) réplicas independentes basta, o sistema só falha se todas falharem juntas:

\[ A_{\text{parallel}} = 1 - (1 - a)^n \]

Duas réplicas com 99% dão \(1 - 0.01^2 = 99.99\%\); três dão 99,9999%. A redundância transforma componentes medíocres em componentes muito disponíveis — desde que suas falhas sejam independentes. O mesmo bug, a mesma configuração ruim, o mesmo host ou a mesma zona quebram essa hipótese.

Aplique as duas regras ao caminho de compra do BoxOffice. Os números são suposições ilustrativas, mas a forma do resultado é geral:

2026-11-01T21:55:08.413266 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/ 0 200 400 600 800 expected minutes per month in which a purchase cannot complete + second payment provider as fallback 99.940% + SQL with automatic failover (Multi-AZ) 99.840% 3 replicas of the stateless services 99.790% 1 instance each 98.301% 26 min/month 70 min/month 92 min/month 745 min/month

Lendo o gráfico de cima para baixo:

  • Com uma instância de tudo, os três serviços com 99,5% dominam: cerca de 98,3%, mais de 12 horas por mês sem compras.
  • Três réplicas dos serviços sem estado os tornam praticamente perfeitos, e o gargalo passa para os componentes que não são replicados: o banco de dados e o provedor externo de pagamento.
  • Failover automático do banco (um standby em outra zona) e um segundo provedor de pagamento atacam esses dois. Cada passo remove o elo mais fraco do momento — e o próximo mais fraco fica visível.
flowchart LR
    fan(["fã"]) --> lb["load balancer<br/>99,99%"]
    lb --> g1["gateway #1"]
    lb --> g2["gateway #2"]
    lb --> g3["gateway #3"]
    g1 --> o["order × 3"]
    g2 --> o
    g3 --> o
    o --> i["inventory × 3"]
    i --> db[("SQL primário<br/>+ standby na AZ-b")]
    o --> p1["provedor de pagamento A<br/>99,9%"]
    o -.->|"fallback"| p2["provedor de pagamento B<br/>99,9%"]

Redundância tem preço

Cada passo do gráfico custa dinheiro (três vezes mais instâncias, um banco em standby, o contrato com um segundo provedor) e complexidade (lógica de failover, testar o fallback). Confiabilidade se compra, não é de graça: o error budget é como você decide quanto comprar.


Redundância e failover

Redundância só ajuda se o tráfego realmente sair do componente que falhou — isso é o failover — e se a cópia redundante estiver pronta para recebê-lo.

Padrão Como funciona Tempo de failover Custo Exemplo no BoxOffice
Ativo-ativo Todas as cópias atendem tráfego; o load balancer para de enviar para a que falhou Segundos (intervalo do health check) Cada cópia precisa absorver a carga extra Réplicas sem estado de gateway, order, catalog
Ativo-passivo (hot standby) Um standby recebe o estado replicado e é promovido na falha Segundos a poucos minutos O standby é pago mas fica ocioso PostgreSQL primário + standby síncrono em outra zona
Cold standby A cópia só é iniciada (ou restaurada) depois de uma falha Minutos a horas O mais barato Workers de analytics, recriados a partir de imagens

Duas regras fazem a redundância funcionar:

  • Capacidade N + 1 (ou N + 2). Se a flash sale precisa de 6 réplicas do order, rode 7 ou 8: depois de perder uma (ou durante um deploy rolling), as restantes ainda precisam aguentar o pico. Com réplicas espalhadas em 3 zonas de disponibilidade, perder uma zona inteira remove um terço da capacidade — planeje para isso.
  • Sem estado sempre que possível. Serviços sem estado fazem failover de forma trivial. O estado (bancos, caches, filas) precisa de replicação, e a replicação traz as perguntas difíceis: síncrona ou assíncrona, quantos dados podem se perder, e como evitar dois primários ao mesmo tempo (split brain; veja Consenso). A página de recuperação de desastres trata do failover entre regiões.

Zonas de disponibilidade e regiões

Uma zona de disponibilidade (AZ) é um ou mais data centers com energia, refrigeração e rede independentes, a alguns quilômetros das outras zonas da mesma região; a latência entre zonas é de cerca de um milissegundo. Espalhar réplicas entre zonas protege contra a falha de um data center a um custo de latência quase nulo. Espalhar entre regiões (a centenas ou milhares de quilômetros) protege contra um desastre regional, mas toda chamada síncrona entre regiões custa dezenas de milissegundos.


Modos de falha

Sistemas distribuídos falham de formas que uma máquina sozinha não falha. Falhas parciais — alguns componentes funcionam e outros não — são muito mais comuns que indisponibilidades totais, e mais difíceis de tratar.

Classe de falha Características Estratégia de tratamento
Crash (fail-stop) O processo morre e continua morto Health checks, reinício, failover para uma réplica
Transitória Se resolve sozinha em segundos (oscilação de rede, eleição de líder) Retry com backoff e jitter
Lenta / gray failure Ainda responde, mas devagar ou com alguns erros; os health checks podem continuar passando Timeouts, circuit breakers, detecção de outliers, SLIs de latência
Intermitente Aparece e some (pausas de GC, um recurso vazando) Latência em percentis, detecção de anomalias, profiling
Bizantina / incorreta Devolve dados errados sem cair (bit flips, cache corrompido, bug) Checksums, validação, reconciliação
Correlacionada Muitos componentes falham juntos (deploy ruim, certificado expirado, queda de zona) Rollouts graduais, diversidade, multi-zona, testes de caos

A mais perigosa delas não é o crash, e sim o componente lento. Uma dependência que caiu responde na hora — com um erro de conexão — e quem chamou segue em frente. Uma dependência lenta segura os recursos de quem chamou enquanto pensa. Quem chama não consegue distinguir lento de morto (veja Sistemas distribuídos), então, sem um prazo, espera.

Falhas em cascata

Uma dependência lenta esgota um recurso de quem a chama — threads, conexões, memória — e quem chama, esgotado, fica lento para os seus chamadores. O esgotamento sobe pelo grafo de chamadas até funcionalidades sem relação falharem. Esse é o jeito mais comum de sistemas de microsserviços caírem3.

sequenceDiagram
    participant F as Fãs
    participant G as gateway
    participant O as order (200 threads)
    participant P as provedor de pagamento
    F->>G: POST /orders (100 por segundo)
    G->>O: cria pedido
    O->>P: cobra (normalmente 200 ms)
    Note over P: degrada: 4 s por chamada
    Note over O: 100/s × 4 s = 400 chamadas em voo > 200 threads
    F->>G: GET /events/ev-88 (catálogo)
    G->>O: (threads do gateway também esperando o order)
    G-->>F: 503 — nenhuma thread livre
    Note over F,G: a navegação também falha, embora o catálogo esteja saudável

A aritmética é a Lei de Little (veja Filas): chamadas em voo = taxa de chegada × tempo de cada chamada.

Latência do pagamento Chamadas em voo a 100 compras/s Pool de threads do order (200)
200 ms (normal) 100 × 0,2 = 20 10% ocupado
1 s 100 × 1 = 100 50% ocupado
4 s 100 × 4 = 400 esgotado: requisições enfileiram e depois dão timeout

Nada na taxa de compras mudou. Um aumento de 20× na latência de uma dependência virou um aumento de 20× nas threads ocupadas. Quando o pool enche, todas as rotas do order esperam — inclusive as que nunca chamam o pagamento — e o gateway, esperando pelo order, enche o próprio pool.

Três coisas param a cascata, cada uma tratada em Padrões de resiliência:

  • Um timeout limita quanto tempo uma chamada pode segurar uma thread (timeout de 1 s → no máximo 100 threads, não 400).
  • Um circuit breaker para de chamar uma dependência que está falhando, então as chamadas falham em microssegundos em vez de segundos.
  • Um bulkhead dá às chamadas de pagamento um pool próprio e limitado, para que elas não tomem as threads de outras rotas.

Experimente no playground

O cenário cascading-failure tem um app chamando uma API de terceiros lenta, sem timeouts. Aperte Run e observe as threads ocupadas do app subirem, depois a latência e os erros, depois os erros de rotas que nem usam o terceiro. Agora adicione um timeout à chamada ao terceiro e rode de novo; depois habilite o circuit breaker e compare a taxa de erros e o p99 das rotas saudáveis. Abra o playground completo para testar um bulkhead.

Outros amplificadores

Cascatas são um exemplo de uma família maior: falhas que se alimentam.

  • Tempestades de retry. Todo chamador repete chamadas que falharam, multiplicando a carga sobre uma dependência justamente quando ela está mais fraca (padrões).
  • Manada (thundering herd) na recuperação. Um serviço volta e todos os clientes, caches e consumidores de fila o atingem no mesmo instante; ele cai de novo.
  • Espiral da morte de autoscaling ou health checks. Instâncias sobrecarregadas falham nos health checks e são removidas, então as restantes recebem mais carga e falham também.
  • Falhas metaestáveis. O sistema continua quebrado depois que o gatilho passou, porque o trabalho gerado pela falha (retries, timeouts, cache misses) o mantém sobrecarregado4. A solução é descartar carga até ele se recuperar — load shedding, retry budgets, circuit breakers.

O mapa de conceitos de confiabilidade

Confiabilidade não é um componente: é uma propriedade que se compra, peça por peça, em toda a paleta. O mapa liga cada componente da paleta (verde) aos conceitos de confiabilidade que ele carrega (azul); as setas mostram como a aula se constrói — redundância e probes 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 que tudo funciona:

flowchart LR
    lbapp["lb · app"]:::comp -.- red["MTTF/MTTR, noves, série vs paralelo,<br/>redundância, N+1, multi-AZ"]:::tech
    tp["thirdparty · payment"]:::comp -.- casc["falhas em cascata<br/>(Lei de Little)"]:::tech
    casc --> pat["timeout, retry + jitter,<br/>circuit breaker, bulkhead, fallback"]:::tech
    apigw["apigw"]:::comp -.- shed["rate limiting,<br/>descarte de carga"]:::tech
    hc["healthcheck"]:::comp -.- probes["liveness, readiness,<br/>startup"]:::tech
    data["sql · objstore"]:::comp -.- dr["RPO / RTO, backups PITR,<br/>estratégias de DR"]:::tech
    dns["dns"]:::comp -.- dr
    obs["metrics · alerting"]:::comp -.- slo["SLI, SLO, orçamento de erros,<br/>burn rate"]:::tech
    probes --> red
    pat --> shed
    pat --> chaos["experimentos de caos,<br/>game days"]:::tech
    dr --> chaos
    slo --> chaos
    classDef comp fill:#1e9e5a22,stroke:#1e9e5a
    classDef tech fill:#2f6fed22,stroke:#2f6fed
Componente da paleta Pergunta que responde Conceitos Ensinado em Cenários
lb, app Perder uma réplica ou uma zona para a compra? MTTF/MTTR, noves, série vs paralelo, ativo-ativo, N+1, multi-AZ esta visão geral scale-out
thirdparty, payment E se uma dependência ficar lenta? falhas em cascata, timeout, retry + backoff + jitter, circuit breaker, bulkhead, fallback esta visão geral, Padrões de resiliência cascading-failure, retry-storm, payment-system
apigw Que trabalho recusamos em sobrecarga? rate limiting por cliente, descarte de carga por prioridade, sala de espera Padrões de resiliência rate-limiter, flash-sale
healthcheck O tráfego deve chegar a esta instância? liveness, readiness, startup; nenhuma dependência no liveness Padrões de resiliência scale-out
metrics, alerting Somos confiáveis o suficiente? SLI, SLO, orçamento de erros, burn rate, política de orçamento — a meta pela qual cada defesa aqui é julgada Observabilidade — SLIs, SLOs e orçamento de erros (aula 9) observability-stack
sql, objstore Quantos dados e quanto tempo podemos perder? RPO/RTO, PITR, backups 3-2-1, réplica atrasada Recuperação de desastres db-failover, db-replicas
dns Como os fãs chegam à região sobrevivente? multirregião, as quatro estratégias de DR, fencing, TTL do DNS Recuperação de desastres multi-region
(todos eles) As defesas funcionam de verdade? hipótese de estado estável, injeção de falhas, raio de impacto, game days Engenharia do caos flash-sale (caos)

Na entrevista

Quando você termina um projeto, entrevistadores costumam perguntar "e se X falhar?" ou "e se X ficar lento?". Responda com uma cadeia: quem chama X, que recurso segura enquanto espera, qual é o timeout, qual é o fallback e o que o usuário vê. Para o BoxOffice: "o pagamento é chamado pelo order com timeout de 2 s, um circuit breaker e um bulkhead próprio de 50 chamadas concorrentes; quando o breaker abre, o fã vê 'pagamento temporariamente indisponível, seus lugares continuam reservados por 10 minutos' — e o catálogo não é afetado." Cite também a matemática da disponibilidade: "duas dependências com três noves em série nos limitam a 99,8%".

Boas práticas

  • Escreva o teto de disponibilidade de cada jornada antes de escolher componentes — multiplique o que o caminho chama de forma síncrona: duas dependências a 99,9% já limitam a compra a 99,8%. No BoxOffice: o gráfico do caminho de compra.
  • Rode N+1 réplicas espalhadas por pelo menos duas zonas — depois de perder uma réplica, ou uma zona inteira, as restantes ainda precisam carregar o pico das 10:00; uma chamada entre zonas custa cerca de 1 ms.
  • Automatize a recuperação antes de buscar um MTTF maior — reiniciar em 1 minuto em vez de 2 horas levou o inventory de 99,72% a 99,998%. No BoxOffice: probes e reinícios no Hands-on 10.
  • Dê a toda dependência um plano de falha escrito: seu timeout, seu fallback e o que o fã vê — decidido no projeto, não às 10:02. No BoxOffice: o payment falha rápido com 503 e o pedido fica PENDING_PAYMENT (Hands-on 9).
  • Tire da requisição tudo o que o fã não espera — cada dependência síncrona baixa o teto da jornada. No BoxOffice: o e-mail sai pelo outbox e pelo Kafka (Hands-on 7).
  • Acompanhe o MTTR como três números: tempo para detectar, diagnosticar e corrigir — cada um é reduzido por uma prática diferente (alertas, traces, rollback). No BoxOffice: a linha do tempo do game day do Hands-on 9.

Armadilhas comuns

  • Nenhum timeout em um cliente HTTP, ou um padrão de minutos: uma dependência lenta segura threads indefinidamente.
  • Supor independência: três réplicas no mesmo nó, ou com a mesma configuração ruim, falham juntas.
  • Redundância sem testar o failover: um standby que ninguém nunca promoveu é uma esperança, não um plano.
  • Pools compartilhados: um único pool de threads ou conexões para todas as dependências deixa a pior decidir por todas.
  • Medir só crashes: uma gray failure (lenta, parcialmente errada) passa nos health checks e prejudica os usuários.

O que quebra em seguida

Os padrões contêm um provedor lento, a redundância e o plano de DR cobrem máquinas e regiões perdidas, e os experimentos de caos provam os dois. Mas toda defesa desta página supõe que algo reinicia uma réplica morta, adiciona réplicas antes das 10:00 e desfaz uma versão ruim — e num laptop com Compose, esse algo é você. O exemplo do inventory acima passou de 99,72% para 99,998% só porque o reinício levou 1 minuto em vez de 2 horas; esse minuto só existe se uma máquina fizer o reinício. Curar, escalar às 09:45 e voltar atrás sem um humano é a aula 11 — Cloud-Native.



  1. IEEE. IEEE Standard Glossary of Software Engineering Terminology, IEEE Std 610.12-1990. ↩

  2. BEYER, B. et al. Site Reliability Engineering: How Google Runs Production Systems. O'Reilly, 2016. Capítulo 3, "Embracing Risk"; capítulo 22, "Addressing Cascading Failures". ↩

  3. NYGARD, M. Release It! Design and Deploy Production-Ready Software, 2. ed. Pragmatic Bookshelf, 2018. Capítulo "Stability Antipatterns". ↩

  4. BRONSON, N. et al. Metastable Failures in Distributed Systems. In: Workshop on Hot Topics in Operating Systems (HotOS), 2021. ↩