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
- Esta visão geral — disponibilidade, MTTF/MTTR, os "noves", disponibilidade composta, redundância e failover, modos de falha e falhas em cascata.
- Padrões de resiliência — timeout, retry com backoff e jitter, circuit breaker, bulkhead, load shedding, rate limiting, health checks, com Resilience4j.
- Recuperação de desastres — RPO e RTO, backups, multi-AZ e multi-região, e as quatro estratégias de DR.
- Engenharia do caos — injetar falhas de propósito para provar que as defesas funcionam; game days.
- 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 |
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:
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:
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:
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
inventoryde 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
paymentfalha rápido com503e o pedido ficaPENDING_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.
Relacionados
- Hands-on (na semana seguinte a esta aula e à aula 9): primeiro o Hands-on 8 — Observabilidade — OpenTelemetry, um dashboard RED e alertas de burn rate de SLO, para que os experimentos tenham o que mostrar; depois o Hands-on 9 — Resiliência e caos: Resilience4j entre
orderepayment, latência e falhas com Toxiproxy, um game day. Hands-on 10 — Kubernetes: probes e autorrecuperação. - Projeto: Game day de caos.
- Estudos de caso: Sistema de pagamentos, Venda de ingressos (flash sale do BoxOffice).
- Aulas: Estimativas — matemática da disponibilidade, Observabilidade (aula anterior, na mesma semana: os SLOs, dashboards e alertas pelos quais estas defesas são julgadas), Mensageria (a assincronia tira dependências do caminho crítico), Cloud-Native (próxima aula: probes, autorrecuperação, autoescala).
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.
-
IEEE. IEEE Standard Glossary of Software Engineering Terminology, IEEE Std 610.12-1990. ↩
-
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". ↩
-
NYGARD, M. Release It! Design and Deploy Production-Ready Software, 2. ed. Pragmatic Bookshelf, 2018. Capítulo "Stability Antipatterns". ↩
-
BRONSON, N. et al. Metastable Failures in Distributed Systems. In: Workshop on Hot Topics in Operating Systems (HotOS), 2021. ↩