Matemática da Disponibilidade
A BoxOffice promete aos organizadores que o checkout estará disponível 99,95% do tempo. Cada equipe relata com orgulho que seu serviço roda com "três noves e meio" — gateway, auth, inventory, order, todos a 99,95%. O contrato do provedor de pagamentos diz 99,9%. E o checkout ainda chama o serviço de e-mail de forma síncrona para enviar o ingresso. No fim do mês, o checkout ficou disponível só 99,2% do tempo — quase seis horas de falhas — e nenhuma equipe deixou de cumprir sua meta. Esta página é a aritmética que prevê esse resultado de antemão e mostra os movimentos de design que o mudam.
Disponibilidade, MTTF e MTTR
Disponibilidade é a fração do tempo — ou das requisições — em que um sistema faz o seu trabalho. Duas formas de medi-la são comuns:
| Tipo | Fórmula | Bom para |
|---|---|---|
| Baseada em tempo | tempo no ar / (tempo no ar + tempo fora do ar) | infraestrutura, "está no ar?" |
| Baseada em requisições | requisições bem-sucedidas / requisições válidas | serviços; o que os usuários realmente experimentam; a base dos SLOs2 |
Para um componente que alterna entre funcionar e estar em reparo, a disponibilidade sai de duas médias:
- MTTF (Mean Time To Failure): quanto tempo ele roda, em média, antes de falhar.
- MTTR (Mean Time To Recovery/Repair): quanto tempo leva, em média, para detectar, diagnosticar e restaurar o serviço.
- MTBF (Mean Time Between Failures) = MTTF + MTTR.
BoxOffice — duas formas de melhorar o serviço de inventário
O serviço de inventário falha cerca de uma vez por mês (MTTF ≈ 720 h); a cada vez, leva 2 h até alguém ser acionado, encontrar o problema e reiniciá-lo (MTTR = 2 h).
A = 720 / 722 ≈ 99,72%.
- Tornar as falhas duas vezes mais raras (MTTF = 1.440 h): A = 1.440 / 1.442 ≈ 99,86%.
- Detectar a falha com um health check e substituir a instância automaticamente em 2 minutos (MTTR ≈ 0,033 h): A = 720 / 720,033 ≈ 99,995%.
Recuperar mais rápido costuma ser a alavanca mais barata. A automação (health checks, reinícios automáticos, failover) ataca o MTTR; testes e redundância atacam o MTTF.
Os noves
Metas de disponibilidade são expressas como um número de "noves". A tabela é calculada para várias janelas — a mesma porcentagem permite indisponibilidades diferentes dependendo do período em que é medida:
| A | 365.25 d | 30.44 d | 28 d | 7 d | 1 d |
|---|---|---|---|---|---|
| 90% | 36.5 d | 3.04 d | 2.8 d | 16.8 h | 2.4 h |
| 99% | 3.65 d | 7.31 h | 6.72 h | 1.68 h | 14.4 min |
| 99.5% | 1.83 d | 3.65 h | 3.36 h | 50.4 min | 7.2 min |
| 99.9% | 8.77 h | 43.8 min | 40.3 min | 10.1 min | 1.44 min |
| 99.95% | 4.38 h | 21.9 min | 20.2 min | 5.04 min | 43.2 s |
| 99.99% | 52.6 min | 4.38 min | 4.03 min | 1.01 min | 8.64 s |
| 99.999% | 5.26 min | 26.3 s | 24.2 s | 6.05 s | 0.864 s |
| 99.9999% | 31.6 s | 2.63 s | 2.42 s | 0.605 s | 0.0864 s |
O cabeçalho de cada coluna é a duração da janela (um ano de 365,25 dias, um mês médio, 28 dias, uma semana, um dia); cada célula é a indisponibilidade permitida naquela janela.
Cada nove a mais é uma redução de 10× na indisponibilidade permitida — e, grosso modo, uma mudança na forma como o sistema precisa ser operado:
| Meta | Permitido por mês | O que exige, grosso modo |
|---|---|---|
| 99% | ~7 h | um servidor, recuperação manual, backups |
| 99,9% | ~44 min | redundância, health checks, humanos de plantão, recuperação ensaiada |
| 99,99% | ~4 min | failover automático (humanos são lentos demais para estar no laço), multi-zona, deploys seguros (canário, rollback instantâneo) |
| 99,999% | ~26 s | multi-região ativo-ativo, tudo automatizado, disciplina extrema de mudanças — muito caro |
Uma conferência de sanidade útil: a 99,99% você não pode se dar ao luxo de um incidente de 5 minutos por mês. Se o seu procedimento de recuperação envolve um humano lendo um painel, você não é um sistema de quatro noves.
Números baseados em requisições são números grandes
A 2.000 requisições por segundo, "99,9% das requisições têm sucesso" ainda significa 2 requisições falhando a cada segundo, ~170.000 por dia. Metas de disponibilidade tratam de taxas, nunca de "nenhum erro" — 100% não é alcançável nem um objetivo sensato. A diferença até 100% é o error budget (aula 9).
Componentes em série
Quando uma requisição precisa que todos os componentes de uma cadeia funcionem (uma cadeia de dependências síncronas), o sistema só funciona se todos funcionarem. Se as falhas forem independentes, as disponibilidades se multiplicam:
A cadeia é sempre pior que seu elo mais fraco, e cada dependência síncrona a mais baixa o teto. O gráfico mostra \(a^n\) para n componentes idênticos:
Dez componentes a 99,9% dão 99,0%; trinta dão 97,0%. Uma arquitetura de microsserviços em que uma requisição atravessa dez serviços de forma síncrona precisa que cada um deles seja muito melhor que a meta do todo.
Componentes em paralelo
Quando qualquer um de n componentes redundantes e independentes basta (réplicas atrás de um load balancer, um primário com um standby, dois provedores de pagamento), o sistema só falha se todos falharem ao mesmo tempo:
| Uma réplica | Duas réplicas | Três réplicas |
|---|---|---|
| 99% | 1 − 0,01² = 99,99% | 1 − 0,01³ = 99,9999% |
| 99,5% | 1 − 0,005² = 99,9975% | 1 − 0,005³ ≈ 99,99999% |
A redundância transforma componentes medíocres em sistemas muito disponíveis — se as falhas forem independentes (veja abaixo) e se o failover realmente funcionar (o load balancer percebe a falha e para de enviar tráfego, o standby de fato assume).
k-de-n: quóruns
Alguns sistemas precisam de uma maioria das réplicas, não de apenas uma — protocolos de consenso e bancos replicados por quórum (aula 3). Com 3 réplicas de disponibilidade a, um quórum de 2-de-3 está disponível quando as 3, ou exatamente 2, estão no ar:
Com a = 99%: 0,970299 + 0,029403 = 99,97% — melhor que um nó, pior que "qualquer uma das três" (99,9999%). O preço da consistência é pago em disponibilidade, até na aritmética.
O checkout da BoxOffice, calculado
O caminho do checkout, como descrito no início da página (as disponibilidades são premissas ilustrativas):
flowchart LR
fan([Fã]) --> edge["Borda + gateway<br/>99,99%"]
edge --> auth["auth<br/>99,95%"]
auth --> order["order<br/>99,95%"]
order --> inv["inventory<br/>99,95%"]
inv --> db[("banco do inventory<br/>99,95%")]
order --> pay["provedor de pagamento<br/>99,9%"]
order --> mail["e-mail (síncrono)<br/>99,5%"] | Design | Cálculo | Disponibilidade | Falhas a cada 30 dias |
|---|---|---|---|
| Ingênuo (tudo síncrono, em série) | 0,9999 × 0,9995⁴ × 0,999 × 0,995 | 99,19% | ~5,8 h |
| E-mail assíncrono (fila + worker; fora do caminho crítico) | 0,9999 × 0,9995⁴ × 0,999 | 99,69% | ~2,2 h |
| + segundo provedor de pagamento (failover quando o primeiro cai) | 0,9999 × 0,9995⁴ × (1 − 0,001²) | 99,79% | ~1,5 h |
| + todo componente interno a 99,99% (réplicas, failover automático, deploys seguros) | 0,9999⁵ × (1 − 0,001²) | 99,95% | ~22 min |
Três lições, em ordem decrescente de custo-benefício:
- Tire dependências do caminho crítico. Tornar o e-mail assíncrono ganhou mais que qualquer outra mudança isolada — e não custou quase nada. Se o usuário não precisa que algo termine antes da resposta, isso não deveria estar na cadeia.
- Acrescente redundância onde uma dependência é fraca e crítica. Um segundo provedor de pagamento transforma uma dependência de 99,9% numa efetivamente de 99,9999% (se as falhas deles forem independentes).
- Faça cada elo restante ser melhor que a meta. Uma cadeia de cinco componentes só cumpre 99,95% se cada um estiver em torno de 99,99%. Essa é a "regra do nove extra": as dependências críticas de um serviço devem ter, cada uma, aproximadamente um nove a mais de disponibilidade que o próprio serviço1.
Dependências suaves não entram na cadeia
Uma dependência cuja falha o serviço consegue tolerar — servindo um valor em cache, um padrão ou uma página parcial — não se multiplica na disponibilidade do caminho crítico. Recomendações, contadores de visualização e o assistente de IA devem ser todos dependências suaves do checkout: se falharem, o checkout continua funcionando. Transformar dependências rígidas em suaves (fallbacks, caches, assíncrono) é uma habilidade de design que você vai praticar na aula 10.
A premissa da independência
Todas as fórmulas acima supõem que os componentes falham de forma independente. Falhas reais costumam ser correlacionadas, e a correlação destrói a fórmula do paralelo:
- Duas réplicas no mesmo host, rack ou zona de disponibilidade caem junto com ele.
- Duas réplicas rodando a mesma versão com bug quebram com a mesma entrada.
- Uma configuração ruim aplicada em todo lugar de uma vez quebra todas as réplicas de uma vez.
- Uma dependência compartilhada — o mesmo provedor de DNS, o mesmo certificado que expira, o mesmo banco — é um ponto único de falha escondido atrás de uma redundância aparente.
- Sobrecarga: quando uma réplica morre, seu tráfego vai para as outras, que podem falhar também (uma falha em cascata).
BoxOffice — três réplicas, uma queda
O serviço de inventário roda 3 réplicas, cada uma 99,5% disponível sozinha: a fórmula do paralelo promete 1 − 0,005³ ≈ 99,99999%. Na prática, a pior queda do ano veio de uma versão com uma configuração errada do pool de conexões, implantada nas três réplicas em 2 minutos. As três falharam juntas, por 40 minutos, até o rollback. A redundância não ajudou, porque a falha não era independente. As correções não são mais réplicas, e sim diversidade no tempo e no espaço: deploys canário (uma réplica primeiro, observar, depois o resto), réplicas espalhadas por zonas e rollback rápido (MTTR menor).
Projetar para a independência — domínios de falha separados (zonas, regiões), implantações graduais, nenhuma dependência compartilhada escondida — é tão importante quanto acrescentar réplicas.
Tempo de detecção e de failover
A redundância só ajuda depois que a falha é detectada e o tráfego é movido. Se um load balancer verifica a saúde a cada 10 s e precisa de 3 falhas seguidas para marcar uma réplica como fora, os usuários atingem a réplica com falha por até ~30 s a cada vez. Com falhas pequenas e frequentes, isso importa:
Dez failovers por ano a 30 s + 30 s cada dão 10 minutos: tudo bem para 99,99% (52 minutos por ano), mas consome um quinto do orçamento. Health checks mais rápidos, novas tentativas do lado do cliente para outra réplica e drenagem de conexões reduzem esse tempo.
Experimente. No cenário scale-out uma réplica do catalog cai em t = 80 s, e nada avisa o load balancer: ele continua mandando a essa réplica sua parte do tráfego por ~30 s. Primeiro dê ao design capacidade para sobreviver à perda — clique em catalog e aumente as réplicas para 5 — e aperte Run: observe a taxa de erros a partir de t = 80 s. Depois abra no playground completo (↗), acrescente um Health Check, ligue-o ao load balancer e rode de novo.
O que você deveria ter observado?
Com 5 réplicas e sem health check, uma requisição em cada cinco vai para a réplica morta: cerca de 20% das requisições falham de t ≈ 81 s a t ≈ 110 s, cerca de 4% da execução inteira de 3 minutos. Com o health check, a réplica sai em 3 s: os erros vão de t ≈ 81 s a 83 s e a execução termina com cerca de 0,4% — a mesma falha, um décimo do estrago, exatamente como indisponibilidade ≈ falhas × tempo de detecção prevê. Com as 2 réplicas do desenho original, a detecção não é o primeiro problema: as duas réplicas estão sobrecarregadas antes da queda (ρ > 1), e perder uma corta a capacidade pela metade de novo.
No código do BoxOffice: health checks passivos e uma nova tentativa (lab 3)
A borda Nginx do Hands-on 3 detecta falhas passivamente, pelas requisições reais: 3 falhas em 10 s tiram uma réplica de rotação por 10 s, e o proxy_next_upstream repete a requisição que falhou uma vez em outra réplica, então o fã não vê o erro. O laboratório mediu os dois tipos de falha sob 150 req/s: uma réplica parada custou quase nada (a conexão é recusada na hora e repetida em outra); uma congelada custou o timeout de leitura — nenhum erro em 14.990 requisições, mas p99 = 1 s e máximo = 5,05 s, o proxy_read_timeout. O tempo de detecção é pago em erros quando ninguém repete a requisição, e em latência quando alguém repete.
upstream catalog {
zone catalog 64k; # shared memory, required by "resolve"
# least_conn; # try it: pick the replica with fewest active requests
server catalog-1:8082 resolve max_fails=3 fail_timeout=10s;
server catalog-2:8082 resolve max_fails=3 fail_timeout=10s;
server catalog-3:8082 resolve max_fails=3 fail_timeout=10s;
keepalive 64; # reuse TCP connections to the replicas
}
# passive health checking: a failed attempt is retried on another replica
proxy_connect_timeout 1s;
proxy_read_timeout 5s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
Pense antes: uma segunda região vale a pena?
A BoxOffice roda em uma região com três zonas de disponibilidade. A documentação do provedor sugere que a queda de uma região inteira é rara, mas, quando acontece, pode durar horas. Uma segunda região (ativo-passivo, com failover por DNS) dobraria aproximadamente o custo de infraestrutura. Como você decidiria?
Esboço de resposta: estime o custo esperado da indisponibilidade contra o custo da segunda região. Se uma queda regional de ~4 h acontece mais ou menos uma vez a cada dois anos (uma premissa a validar com o histórico do seu provedor), a indisponibilidade esperada é de ~2 h/ano — quase metade das ~4,4 h/ano que uma meta de 99,95% permite, antes de qualquer outro incidente. Depois considere quando ela acontece: uma queda regional durante a abertura de vendas de um estádio custa ao negócio muito mais que uma às 4 da manhã de uma terça-feira. Se o SLA com os organizadores prevê multas, ou se uma abertura de vendas perdida custa mais que um ano de uma segunda região, vale a pena — e aí a parte difícil são os dados (aula 3 e aula 10), não os servidores.
Na entrevista
Ao desenhar um design de alto nível, faça o cálculo em série em voz alta para o caminho crítico: "a requisição atravessa cinco componentes; a 99,9% cada, isso dá cerca de 99,5%, então, para prometer 99,9% no todo, preciso de redundância em cada um e tiraria o e-mail do caminho." Depois mencione falhas correlacionadas — "réplicas em zonas diferentes, deploys canário" —, o que mostra que você sabe onde as fórmulas deixam de valer. Fale de indisponibilidade com uma janela: "quatro noves são cerca de quatro minutos por mês".
Boas práticas
- Desenhe o caminho crítico e calcule sua disponibilidade antes de prometer um SLO — multiplique os elos; se o produto ficar abaixo da promessa, mude o design, não a redação da promessa. Na BoxOffice: a cadeia do checkout nesta página.
- Automatize detecção e recuperação — health checks a cada poucos segundos, reinício automático, uma nova tentativa em outra réplica; o MTTR é a alavanca barata. Na BoxOffice:
max_failseproxy_next_upstreamnonginx.confdo lab 3 (quadro acima), e o health check do gateway a cada 5 s no lab 4. - Treine o failover com agenda e cronometre — derrube uma réplica ou um primário num ambiente de teste e meça detecção + failover contra o orçamento. Na BoxOffice: o passo 5 do Hands-on 3 (parar e congelar uma réplica) e o game day do lab 9.
- Acompanhe o orçamento de erro, não só o uptime — 99,95% das requisições em 28 dias são cerca de 20 minutos de falha total; gaste-o de propósito (versões, experimentos), não por acidente.
- Dê a toda dependência um timeout e um fallback — o timeout limita quanto tempo uma dependência fraca pode machucar; o fallback a mantém branda. Na BoxOffice: o timeout de leitura de 1 s de
order → paymentno lab 9.
Armadilhas comuns
- Citar uma meta sem janela: 99,9% são 43,8 min num mês médio, mas 40,3 min em 28 dias.
- Ignorar a cadeia: cada equipe cumpre sua meta e o produto perde a dele.
- Supor independência: réplicas na mesma zona, na mesma versão, atrás do mesmo DNS.
- Contar redundância sem failover: um standby que nunca é testado não é redundância.
- Perseguir noves em toda parte: caminhos diferentes merecem metas diferentes; cinco noves para o widget de recomendações é dinheiro jogado fora.
Como se conecta
flowchart LR
MT["A = MTTF / (MTTF + MTTR)"] --> NINES["noves numa janela<br/>→ SLOs de Confiabilidade"]
SER["série: ∏ aᵢ"] --> CHAIN["caminho crítico: borda, auth,<br/>order, inventory, provedor"]
CHAIN --> ASYNC["tirar dependências do caminho<br/>queue + worker → Mensageria"]
PAR["paralelo: 1 − ∏(1 − aᵢ)"] --> RED["réplicas,<br/>um segundo provedor"]
QUO["quóruns k-de-n<br/>→ Distribuídos"] --> RED
RED --> IND["independentes? zonas,<br/>versões, configuração"]
IND --> DR["multi-AZ, multirregião<br/>→ Confiabilidade"]
MT --> DET["tempo de detecção + failover<br/>health checks"]
DET --> RED Componentes da paleta: lb · healthcheck (tempo de detecção = intervalo × falhas para marcar como fora) · app (réplicas em paralelo, se as falhas forem independentes) · sql (primário + standby com failover automático) · payment (uma dependência de 99,9%; um segundo provedor) · email (retirado do caminho crítico) · queue · worker (o caminho assíncrono) · dns (failover multirregião). Cenários: scale-out, multi-region, payment-system, notification-system. Continua em: Distribuídos — consenso (quóruns), Observabilidade — SLOs, Confiabilidade — multi-AZ e multirregião, Compute — quedas do provedor, Exercício: design doc.
Pontos-chave
- A = MTTF / (MTTF + MTTR); reduzir o MTTR com automação costuma ser a alavanca mais barata.
- Cada nove é 10× menos indisponibilidade; sempre diga a janela.
- Em série, multiplica (pior que o elo mais fraco); em paralelo, dá 1 − ∏(1 − aᵢ); quóruns ficam no meio.
- Melhore uma cadeia tirando dependências rígidas do caminho crítico, acrescentando redundância aos elos fracos e fazendo cada elo ter ~um nove a mais que a meta.
- As fórmulas supõem falhas independentes; falhas correlacionadas (mesma zona, mesma versão, mesma configuração) são o que de fato derruba sistemas.
-
TREYNOR, B.; DAHLIN, M.; RAU, V.; BEYER, B. The Calculus of Service Availability. ACM Queue, 15(2), 2017. ↩
-
BEYER, B. et al. Site Reliability Engineering. Google / O'Reilly, 2016. Chapter 3 (embracing risk). ↩