Panorama da resiliência

TL;DR

Num monólito, ou o sistema está no ar ou não está. Distribuído, ele fica meio no ar — e esse estado intermediário é o que os padrões desta família administram. O inimigo não é a dependência que cai: é a que fica lenta, porque a lentidão consome recursos do chamador até que ele também pare, e a falha sobe a cadeia até o usuário. Cada padrão aqui corta essa corrente num ponto — e nenhum é de graça. A lente da família é essa: todo padrão de resiliência é uma escolha sobre o que sacrificar para não cair inteiro, e sobre quem paga a conta.

O recorte desta família

Esta é a família mais coberta do vault, e a redundância é deliberada — um catálogo de padrões precisa ter uma entrada para “Circuit Breaker”, não um ponteiro. Mas o recorte é estreito: aqui está o trade-off explícito de cada padrão. Quanto aguenta está em System Design 3-05; como tunar, operar e testar em Operação 3-06; qual serviço gerenciado faz isso em Cloud 20; como migrar com Strangler Fig e ACL na Arqueologia.

O serviço que não caiu — e derrubou todo o resto

O serviço de recomendação ficou lento. Não caiu: passou a responder em oito segundos em vez de oitenta milissegundos.

A página de produto chama recomendação. Cada requisição de página passou a ocupar uma thread por oito segundos em vez de por um décimo de segundo — oitenta vezes mais tempo. Com o mesmo tráfego de sempre, o pool de threads da página de produto encheu em menos de um minuto.

Cheio o pool, a página de produto parou de responder — inclusive para requisições que não precisavam de recomendação. O balanceador marcou as instâncias como não saudáveis e tirou-as de rotação, o que concentrou o tráfego nas restantes, que encheram mais rápido. Em quatro minutos, a loja inteira estava fora do ar por causa de um serviço secundário, que continuava respondendo — só que devagar.

Duas lições estão nessa cena, e elas organizam a família toda.

A primeira: a lentidão é pior que a queda. Uma dependência que recusa conexão devolve erro em milissegundos, e o chamador segue em frente. Uma dependência lenta retém os recursos do chamador — threads, conexões, memória de requisições em voo — e é essa retenção que propaga a falha. Nygard nomeia isso: os pontos de integração são a origem da maioria dos incidentes, e o mecanismo é quase sempre thread bloqueada, não erro.

A segunda: sistemas distribuídos falham em cascata. A falha não fica onde nasceu; ela sobe pela cadeia de chamadas, e cada camada que não se defende repassa e amplifica.

Falha parcial: o modo que o monólito não tinha

Num processo único, uma chamada de função ou retorna ou lança exceção — e você sabe imediatamente qual. Atravessando a rede, surgem estados que não existiam:

  • a requisição não chegou;
  • chegou, foi executada, e a resposta se perdeu (você não sabe se o efeito ocorreu);
  • está demorando — e você não consegue distinguir “lento” de “nunca vai responder”;
  • responde, mas com dados velhos de uma réplica atrasada.

O terceiro é o que dá origem a esta família inteira: não há como saber se vale a pena continuar esperando. Todo padrão aqui é, no fundo, uma heurística para decidir isso sem informação completa — e é por isso que todos erram em algum caso, e por isso que todos custam algo.

O mapa: onde cada padrão corta a corrente


graph TD
    U["Usuário"] --> A["Seu serviço"]
    A -->|"chamada"| D["Dependência<br/>lenta ou fora"]

    T["<b>Timeout</b><br/>não espero para sempre"] -.->|"corta a espera"| A
    R["<b>Retry</b><br/>tento de novo, com recuo"] -.->|"cobre o transitório"| A
    C["<b>Circuit Breaker</b><br/>paro de tentar"] -.->|"corta a tentativa"| A
    B["<b>Bulkhead</b><br/>isolo os recursos"] -.->|"contém o estrago"| A
    F["<b>Fallback</b><br/>respondo pior"] -.->|"salva a resposta"| A
    L["<b>Rate limit / shedding</b><br/>recuso na entrada"] -.->|"protege da carga"| A

    style D fill:#D0021B,color:#fff
    style A fill:#4A90D9,color:#fff
    style T fill:#4A90D9,color:#fff
    style R fill:#F5A623,color:#000
    style C fill:#4A90D9,color:#fff
    style B fill:#4A90D9,color:#fff
    style F fill:#4A90D9,color:#fff
    style L fill:#4A90D9,color:#fff

O âmbar no Retry é intencional: é o único padrão do mapa que piora o problema quando mal configurado — todos os outros, no pior caso, são inúteis; o retry ingênuo transforma um serviço fraco num serviço morto.

Os padrões se dividem por para onde olham: Timeout, Retry, Circuit Breaker e Fallback olham para fora (protegem você da sua dependência); Bulkhead, Rate Limiting e Load Shedding olham para dentro (protegem você de quem te chama, e de você mesmo).

A lente: o que se sacrifica, e quem paga

Nenhum padrão desta família é gratuito. Cada um compra sobrevivência com uma moeda, e vale saber qual antes de adotar:

PadrãoO que sacrificaQuem paga
Timeoutrequisições que teriam sucesso se esperassem maiso usuário daquela requisição
Retrylatência do caso ruim; amplifica cargao alvo já fraco, e o usuário que espera
Circuit Breakerrequisições que talvez funcionassemusuários durante a janela aberta
Bulkheadutilização de recursos (capacidade ociosa reservada)o orçamento de infraestrutura
Fallbackcorreção — você responde pior de propósitoo usuário, muitas vezes sem saber
Rate limitingclientes legítimos que passaram da cotaos clientes na cauda da distribuição
Load sheddingrequisições escolhidas para morrerquem tiver menor prioridade
Cache-asidefrescor do dadoquem lê algo desatualizado

A soma dos sacrifícios

Aqui está o erro mais caro da família, e ele não é sobre nenhum padrão individual: os sacrifícios se somam, e quase ninguém soma.

Três exemplos que aparecem em incidentes reais:

  • Retry em camadas. O cliente tenta 3 vezes, o gateway tenta 3, o serviço tenta 3. Uma requisição do usuário vira 27 no alvo — exatamente quando ele está mal. Cada camada foi configurada isoladamente, com uma decisão razoável.
  • Timeout mal ordenado. O chamador tem timeout de 2s e o chamado, de 10s. O chamado continua trabalhando para uma resposta que ninguém vai receber — e retém recursos por 8 segundos inúteis, em cada requisição.
  • Retry mais circuit breaker mal casados. As retentativas contam como falhas para o breaker e o abrem mais rápido do que o previsto — ou não contam, e o breaker nunca abre porque a camada de retry esconde as falhas dele.

A regra prática: a ordem de composição importa, e a convencional é bulkhead( breaker( retry( timeout( chamada ) ) ) ) — o timeout é o mais interno porque delimita cada tentativa; o retry envolve tentativas; o breaker observa o resultado do conjunto; o bulkhead limita quantos disso existem em paralelo. Trocar essa ordem muda o comportamento de formas não óbvias, e essa é a discussão que a nota de fechamento retoma.

Armadilhas comuns

Proteger-se da queda e não da lentidão

O que acontece: o sistema trata bem connection refused e não tem defesa alguma contra respostas de oito segundos — que é o modo de falha que efetivamente derruba tudo. Por quê: a queda é o que se imagina ao pensar em falha, e é fácil de simular em teste. A lentidão é mais comum, mais destrutiva e quase nunca testada. Como evitar: teste com latência injetada, não só com o alvo desligado. E trate timeout como configuração obrigatória de toda chamada remota, não como afinação posterior.

Empilhar padrões sem somar os efeitos

O que acontece: retry no cliente, no mesh e na aplicação; breakers em duas camadas; timeouts que se contradizem. Sob incidente, o sistema se comporta de um jeito que ninguém consegue prever, e o mecanismo de defesa vira parte da causa. Por quê: cada padrão foi adicionado por um bom motivo local, por pessoas diferentes, em momentos diferentes. Como evitar: trate a configuração de resiliência como um conjunto — documentada num lugar, com a ordem de composição explícita e os timeouts coerentes de fora para dentro.

Resiliência que nunca roda

O que acontece: o fallback tem um bug, o breaker está configurado com um limiar que nunca é atingido, e ninguém sabe — porque esse código só executa quando algo dá errado, e nada dá errado em teste. Por quê: o caminho de falha é, por definição, o caminho não exercitado. Cobertura de teste alta convive perfeitamente com resiliência quebrada. Como evitar: exercite a falha de propósito — injeção de falha e latência no ambiente de teste, e experimentos controlados em produção onde houver maturidade. Um mecanismo de resiliência nunca acionado é uma hipótese, não uma proteção.

Como explicar em inglês

“In a monolith a call either returns or throws. Across a network you get states that didn’t exist before — and the one that causes most outages isn’t the dependency going down, it’s the dependency going slow. A refused connection fails in milliseconds and you move on; a slow dependency holds your threads, your pool fills up, and you stop serving requests that didn’t even need it. That’s a cascading failure. Every pattern in this family cuts that chain somewhere, and the thing I’d emphasise is that none of them is free: a timeout sacrifices requests that would have succeeded, a circuit breaker sacrifices requests that might have worked, a bulkhead sacrifices utilisation, a fallback sacrifices correctness. The expensive mistake isn’t picking the wrong one — it’s stacking several without adding up the sacrifices, like retries at three layers turning one user request into twenty-seven at a service that’s already struggling.”

PTEN
falha parcialpartial failure
falha em cascatacascading failure
ponto de integraçãointegration point
esgotamento de poolpool exhaustion
degradação graciosagraceful degradation
injeção de falhafault injection
amplificação de cargaload amplification

O que vem a seguir

O primeiro padrão é também o mais simples e o mais esquecido — e é o que ataca diretamente o mecanismo da cena de abertura: a espera indefinida que retém recursos. Sem ele, nenhum dos outros funciona, porque todos pressupõem que uma tentativa termina.

  • 02 - Timeout — a defesa mais básica, e o default que derruba sistemas.
  • 03 - Retry — o único padrão da família que pode piorar o incidente.
  • 04 - Circuit Breaker — parar de bater numa porta que não abre.

Veja também

Fontes

  • Michael NygardRelease It! (2ª ed., 2018) — a fonte canônica de stability patterns e antipadrões; a análise de pontos de integração e threads bloqueadas.
  • MicrosoftCloud Design Patterns — o catálogo que dá nome à maioria dos padrões desta família.
  • Netflix Technology Blog — os escritos sobre Hystrix e tolerância a falhas — a linhagem prática de circuit breaker e bulkhead em produção.
  • Google SRE BookAddressing Cascading Failures — o mecanismo da cascata e as contramedidas.