Introdução: o dia em que o frete derrubou o checkout
Imagine uma loja virtual na Black Friday. O checkout está no ar, o banco está saudável, a CPU está tranquila. De repente, os pedidos começam a falhar. A causa? Um serviço de cotação de frete que não caiu, só ficou lento.
Essa é a pegadinha dos sistemas distribuídos: uma dependência lenta costuma ser mais perigosa que uma dependência morta. Se ela cai, você recebe um erro rápido. Se ela só demora, as threads ficam presas esperando, o pool de conexões enche e a lentidão se espalha pela cadeia até derrubar quem nem dependia dela.
É aqui que entra o Chaos Engineering: a prática de injetar falhas de forma controlada para descobrir como o sistema realmente se comporta, antes que um incidente real faça essa descoberta por você.
Por que quebrar coisas de propósito?
Porque a alternativa é quebrar sem querer, no pior horário possível. Um experimento de caos bem feito tem três características:
- Hipótese clara: sabemos o que esperamos que aconteça.
- Limites de segurança: sabemos até onde o impacto pode ir e quando parar.
- Aprendizado registrado: cada execução vira melhoria concreta.
Quanto a staging versus produção: comece sempre em staging. Produção só entra depois de o time dominar o processo, com tráfego mínimo, aprovação explícita e um botão de interrupção já testado.
A mudança de mentalidade
| Mentalidade tradicional | Mentalidade de resiliência |
|---|---|
| "Como impedimos que isso falhe?" | "Quando isso falhar, o que acontece com o usuário?" |
| Falha é exceção | Falha é condição normal de operação |
| Descobrimos fragilidades em incidentes | Descobrimos fragilidades em experimentos |
| Culpa-se a pessoa | Investiga-se o sistema |
Prevenir continua importante. Mas, além de prevenir, precisamos detectar, conter, degradar com elegância e recuperar.
Três conceitos que sustentam tudo
- Estado estável: os indicadores que definem o "normal" (taxa de sucesso, latência p95, taxa de erros). Sem essa linha de base, você não sabe se o que viu foi efeito do experimento ou só oscilação.
- Blast radius (raio de explosão): o alcance máximo permitido do impacto. Quanto menor no começo, mais seguro.
- Observabilidade: métricas, logs e traces que permitem enxergar o que está acontecendo. Chaos sem observabilidade é só bagunça.
Dissecando o ChaosEngineeringMasterDeck
Pense num baralho em que cada carta é um experimento. A ideia é simples: em vez de planos soltos em um documento qualquer, cada falha que queremos testar vira uma carta com campos obrigatórios. Se um campo não está preenchido, a carta não pode ser jogada.
Uma carta do Master Deck tem estes campos:
- Hipótese: o que esperamos que aconteça.
- Alvo e variável: o que será perturbado e como (tipo de falha, intensidade, duração).
- Estado estável: as métricas de controle e seus limites.
- Blast radius: quem e o que pode ser afetado.
- Condições de aborto: quando o experimento é interrompido.
- Observabilidade: onde olhar durante a execução.
- Aprendizado: o que descobrimos e quais ações geram.
O deck não substitui ferramentas de monitoramento nem a revisão humana. Ele padroniza o raciocínio, para que qualquer pessoa do time consiga planejar e entender um experimento.
Estudo de caso: "Frete Lento"
A arquitetura
┌──────────────────┐
Cliente ──────► │ API de Pedidos │ ──────► Banco de dados
│ (Spring Boot) │
└────────┬─────────┘
│ chamada HTTP
▼
┌──────────────────┐
│ Serviço de Frete │ ◄── latência injetada aqui
└──────────────────┘
A API de pedidos, em Spring Boot, consulta o serviço de frete para calcular o valor da entrega antes de confirmar o pedido. A falha escolhida é latência elevada no serviço de frete, sem derrubá-lo e sem alterar nenhum dado.
A carta jogada
carta: frete-lento
hipotese: >
Se o serviço de frete responder com latência alta, a API de pedidos
vai interromper a espera por chamada, abrir o circuit breaker e usar
uma tabela de frete padrão. O cliente consegue finalizar o pedido
e o impacto não passa do grupo de teste.
alvo: servico-de-frete
variavel:
tipo: latencia-adicional
intensidade: acima do timeout configurado
duracao_maxima: 5 minutos
ambiente: staging
blast_radius:
usuarios: tráfego sintético de teste
dados: nenhuma escrita fora do ambiente de staging
estado_estavel:
- taxa_de_sucesso_dos_pedidos
- latencia_p95_do_checkout
- taxa_de_erros_5xx
- threads_ocupadas
abortar_se:
- taxa_de_sucesso_abaixo_do_limite_aprovado
- erros_5xx_acima_do_limite
- alerta_critico_de_saturacao
Os limites numéricos devem sair dos SLOs do time. Não copie esta carta como configuração de produção.
Blast radius e salvaguardas
- Ambiente: somente staging, sem clientes reais.
- Alcance: apenas o serviço de frete; nenhum outro ambiente é tocado.
- Duração: no máximo 5 minutos.
- Responsável por abortar: definido antes de começar, com o mecanismo de interrupção testado.
- Comunicação: o time avisado antes da execução.
Para levar o mesmo experimento à produção, o caminho seria: tráfego canário, percentual mínimo de usuários, janela combinada e aprovação explícita.
Os padrões de resiliência sob teste
Na API de pedidos usei Resilience4j com três proteções: circuit breaker, bulkhead e fallback. O timeout de 2 segundos fica no cliente HTTP.
resilience4j:
circuitbreaker:
instances:
frete:
slidingWindowType: COUNT_BASED
slidingWindowSize: 20
slowCallDurationThreshold: 2s
slowCallRateThreshold: 50
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 5
bulkhead:
instances:
frete:
maxConcurrentCalls: 10
maxWaitDuration: 0
@Service
public class FreteClient {
private final RestClient restClient; // readTimeout configurado em 2s
public FreteClient(RestClient restClient) {
this.restClient = restClient;
}
@CircuitBreaker(name = "frete", fallbackMethod = "fretePadrao")
@Bulkhead(name = "frete")
public CotacaoFrete cotar(String cep, BigDecimal pesoKg) {
return restClient.get()
.uri("/frete?cep={cep}&peso={peso}", cep, pesoKg)
.retrieve()
.body(CotacaoFrete.class);
}
// Chamado quando o circuito está aberto, o bulkhead está cheio ou a chamada falha
private CotacaoFrete fretePadrao(String cep, BigDecimal pesoKg, Throwable causa) {
return CotacaoFrete.tabelaPadrao(cep, pesoKg); // valor estimado, marcado como tal
}
}
Por que cada peça importa:
- Timeout: impede que uma chamada espere para sempre.
- Circuit breaker: depois de várias chamadas lentas, para de chamar o frete por um tempo e evita acumular espera.
- Bulkhead: limita quantas chamadas ao frete podem ocorrer ao mesmo tempo, protegendo o restante das threads da aplicação.
- Fallback: usa uma tabela de frete padrão. Atenção: só é válido porque o valor é uma estimativa claramente identificada. Um fallback que entrega dado errado como se fosse certo é pior que um erro.
Duas ressalvas importantes: retentativas sem limite podem transformar lentidão em avalanche, e Auto Scaling não resolve uma dependência lenta, podendo até aumentar a pressão sobre ela.
Observabilidade e resultados
Como a falha foi monitorada
- Métricas (Micrometer + Prometheus + Grafana): taxa de sucesso, latência p95, erros 5xx, estado do circuit breaker e threads ocupadas.
- Traces distribuídos: mostram em qual salto da cadeia o tempo está sendo gasto.
- Logs: registram cada abertura do circuito e cada uso do fallback.
- Alertas: confirmam se o time seria avisado sem precisar olhar o painel.
Resultado ilustrativo
| Momento | O que esperamos observar |
|---|---|
| Antes da falha | Estado estável: sucesso alto, p95 baixo, circuito fechado |
| Primeiros segundos | p95 do checkout sobe; chamadas ao frete batem no timeout |
| Após ~10 chamadas lentas | Circuit breaker abre; fallback assume |
| Durante a falha | Pedidos continuam sendo finalizados com frete estimado; p95 volta perto do normal |
| Fim da falha | Circuito vai para meio-aberto, testa algumas chamadas e fecha |
A arquitetura sobreviveu? Na hipótese, sim, mas com degradação: o cliente continua comprando, só que com frete estimado em vez do valor exato. A arquitetura degradou em vez de quebrar, que é exatamente o comportamento que queríamos validar.
Isso não prova resiliência em todos os cenários. Numa execução real, você registra as métricas observadas, compara com os SLOs e documenta qualquer desvio.
E se o resultado for ruim?
Ótimo, foi para isso que o experimento serviu. Algumas descobertas comuns:
- timeout maior do que o previsto, acumulando threads;
- alerta que dispara tarde demais, ou que nem dispara;
- fallback que não cobre todos os fluxos;
- painel que mostra sintomas, mas não aponta o frete como causa.
Cada achado vira uma tarefa priorizada no backlog, não só uma linha no relatório.
Conclusão: resiliência é prática, não promessa
O que levei dessa experiência:
- Falhas lentas são traiçoeiras. Teste latência, não só queda total.
- Hipótese antes de ação. Uma carta preenchida obriga a pensar no que se espera e no que fazer se der errado.
- Blast radius pequeno primeiro. Confiança se constrói em degraus.
- Observabilidade é pré-requisito. Sem ela, o experimento não ensina nada.
- O resultado é o aprendizado, não o "sistema não caiu".
No plano cultural, a prática muda a conversa entre desenvolvimento e operações. Em vez de procurar culpados depois do incidente, o time investiga o sistema junto, com evidências, e melhora as defesas antes do problema aparecer.
Se você quer começar hoje: escolha uma dependência do seu sistema, escreva uma hipótese, defina o estado estável e prepare a forma de interromper antes de injetar a falha. O caos só é útil quando é controlado.
Já rodou algum experimento de caos no seu time? Conta nos comentários qual foi a maior surpresa.
Top comments (0)