DEV Community

Davi Sousa Oliveira
Davi Sousa Oliveira

Posted on

Sobrevivendo ao Caos: Injetando Falhas com o ChaosEngineeringMasterDeck

Como transformar "e se tudo der errado?" em um jogo de cartas que deixa o seu sistema mais forte.


Toda equipe tem aquela história: sexta-feira à tarde, um serviço de pagamento começa a responder devagar, o checkout trava, os alertas disparam e ninguém sabe ao certo o que está quebrando nem por quê. O pior é a frase que vem depois: "mas isso nunca tinha acontecido em staging".

Neste artigo eu mostro como a nossa equipe passou a descobrir esses problemas antes dos clientes, usando Chaos Engineering e um artefato que batizamos de ChaosEngineeringMasterDeck: um baralho de experimentos de falha que organiza o caos de forma segura, repetível e até divertida.

1. Introdução ao Chaos Engineering

O que é

Chaos Engineering é a disciplina de realizar experimentos controlados em um sistema para ganhar confiança de que ele aguenta condições turbulentas em produção. A prática foi popularizada pela Netflix, que criou o Chaos Monkey para derrubar instâncias aleatoriamente e forçar os times a construírem serviços tolerantes a falhas.

Não se trata de "quebrar coisas por quebrar". É método científico aplicado à confiabilidade:

  1. Definir o comportamento normal do sistema (o estado estável);
  2. Formular uma hipótese de que ele continuará normal sob uma falha;
  3. Injetar a falha de forma controlada;
  4. Comparar o resultado com a hipótese;
  5. Corrigir o que for descoberto e repetir.

Por que quebrar coisas de propósito?

Em sistemas distribuídos, a pergunta nunca é "se" algo vai falhar, e sim "quando". Redes particionam, discos enchem, dependências ficam lentas, zonas de disponibilidade caem. Testes unitários e de integração validam a lógica, mas não revelam como os componentes interagem sob estresse: timeouts mal configurados, retries que amplificam o problema (retry storm), filas que crescem sem limite, fallbacks que nunca foram exercitados.

Descobrir isso às 3 da manhã, em um incidente real, custa caro. Descobrir em um experimento planejado, com a equipe de prontidão e um botão de aborto, custa uma tarde.

A mudança de mentalidade

Mentalidade tradicional Mentalidade do Chaos Engineering
Evitar falhas a todo custo Assumir que falhas acontecerão
Medir sucesso por "tempo sem incidentes" Medir por tempo de recuperação e degradação graciosa
Descobrir fragilidades em incidentes Descobrir fragilidades em experimentos
Confiar na arquitetura "no papel" Validar a arquitetura com evidências
Confiabilidade é tarefa do time de Ops Confiabilidade é responsabilidade de todos

Staging ou produção?

O ideal, segundo os princípios da área, é experimentar em produção, porque só lá existe o tráfego, a escala e as dependências reais. Mas chegamos lá gradualmente: começamos em staging, depois produção com raio de explosão (blast radius) minúsculo, e só então ampliamos. Quem começa derrubando produção inteira não está fazendo engenharia, está causando um incidente.

2. Dissecando o ChaosEngineeringMasterDeck

O que é o Master Deck?

No nosso projeto, o ChaosEngineeringMasterDeck é um framework de experimentos em formato de baralho. Cada carta é um experimento de caos padronizado, documentado em um arquivo YAML versionado no repositório. O "Master" vem de ser o baralho central: uma biblioteca viva, mantida pela equipe, que cresce a cada incidente real (todo post-mortem gera uma carta nova).

A gamificação tem propósito: nos Game Days (dias de simulação de desastres), o time "compra" uma carta, lê o cenário e executa o experimento. Isso torna o exercício leve, reduz o medo de falhar e facilita o aprendizado coletivo.

Anatomia de uma carta

Cada carta responde às mesmas perguntas, garantindo que nenhum experimento seja improvisado:

Campo Pergunta que responde
Nome / Naipe Que tipo de falha é? (Rede, Computação, Dados, Dependência, Infraestrutura)
Estado estável Quais métricas definem "tudo normal"?
Hipótese O que acreditamos que vai acontecer?
Variáveis (injeção) Que falha será injetada, com qual intensidade e duração?
Blast Radius Quem pode ser afetado? Qual o limite?
Métricas de controle O que observar durante o experimento?
Condições de aborto Quando interromper imediatamente?
Rollback Como desfazer a injeção?

Os naipes do baralho

  • ♠ Rede: latência, perda de pacotes, partição de rede, DNS falhando.
  • ♥ Computação: instância ou pod morto, CPU saturada, memória esgotada.
  • ♦ Dados: banco indisponível, failover de réplica, disco cheio, replicação atrasada.
  • ♣ Dependência: API externa lenta, erros 5xx de terceiros, certificado expirado.
  • ★ Coringa: cenários de larga escala, como a queda de uma zona de disponibilidade inteira.

Exemplo de carta em YAML

carta:
  id: NET-007
  nome: "Latência Fantasma"
  naipe: rede
  nivel_de_risco: medio
  estado_estavel:
    - "p95 do checkout < 800 ms"
    - "taxa de erro do checkout < 1%"
    - "taxa de conversão dentro de ±5% da média da hora"
  hipotese: >
    Se o payment-service ficar com 2 s de latência adicional,
    o circuit breaker do checkout-service abrirá em até 30 s e o
    fallback (pagamento assíncrono) manterá a taxa de erro abaixo de 2%.
  injecao:
    alvo: payment-service
    tipo: latencia
    atraso_ms: { min: 2000, max: 3000 }
    duracao: 10m
  blast_radius:
    ambiente: staging -> producao (canary)
    percentual_trafego: 5
    contas_de_teste_primeiro: true
  metricas_de_controle:
    - latencia_p95_checkout
    - taxa_de_erro_5xx
    - estado_do_circuit_breaker
    - profundidade_da_fila_de_pagamentos
  aborto_automatico:
    - "taxa de erro do checkout > 5% por 1 min"
    - "p95 do checkout > 3 s por 2 min"
  rollback: "Desativar assault do Chaos Monkey (POST /actuator/chaosmonkey/disable)"
Enter fullscreen mode Exit fullscreen mode

Essa estrutura é o coração do método: sem hipótese, é só bagunça; sem condição de aborto, é irresponsabilidade.

3. Estudo de Caso: a carta "Latência Fantasma"

O cenário de arquitetura

Nosso e-commerce roda na AWS, com a seguinte topologia simplificada:

Cliente --> ALB --> [ checkout-service (Spring Boot) ] --HTTP--> [ payment-service (Spring Boot) ] --> Gateway externo
                              |                                          |
                              +--> inventory-service                     +--> RDS PostgreSQL (Multi-AZ)
                              +--> fila SQS "pagamentos-pendentes"
Enter fullscreen mode Exit fullscreen mode
  • checkout-service e payment-service rodam em ECS/Fargate, em duas zonas de disponibilidade, com Auto Scaling por CPU e latência.
  • O checkout-service chama o payment-service de forma síncrona e usa Resilience4j para proteção.
  • Como fallback, se o pagamento não responder, o pedido é aceito como "pagamento em processamento" e uma mensagem é enfileirada no SQS para cobrança assíncrona.

A carta sorteada

No Game Day, o time sorteou NET-007, "Latência Fantasma": uma dependência crítica não cai, fica lenta. É um dos cenários mais traiçoeiros, porque uma falha total é fácil de detectar, enquanto uma lentidão silenciosa consome threads, conexões e paciência dos usuários.

Hipótese: com 2 a 3 segundos de latência extra no payment-service, o circuit breaker abrirá em até 30 s e o fallback manterá a taxa de erro do checkout abaixo de 2%.

Configuração de resiliência sob teste

# application.yml do checkout-service
resilience4j:
  timelimiter:
    instances:
      payment:
        timeout-duration: 1500ms
  circuitbreaker:
    instances:
      payment:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 20
        failure-rate-threshold: 50
        slow-call-duration-threshold: 1000ms
        slow-call-rate-threshold: 60
        wait-duration-in-open-state: 30s
        permitted-number-of-calls-in-half-open-state: 5
  retry:
    instances:
      payment:
        max-attempts: 2
        wait-duration: 200ms
Enter fullscreen mode Exit fullscreen mode
@CircuitBreaker(name = "payment", fallbackMethod = "pagamentoAssincrono")
@TimeLimiter(name = "payment")
@Retry(name = "payment")
public CompletableFuture<ResultadoPagamento> cobrar(Pedido pedido) {
    return CompletableFuture.supplyAsync(() -> paymentClient.cobrar(pedido));
}

// Fallback: aceita o pedido e enfileira a cobrança para processamento assíncrono
private CompletableFuture<ResultadoPagamento> pagamentoAssincrono(Pedido pedido, Throwable t) {
    sqs.enviar("pagamentos-pendentes", pedido);
    return CompletableFuture.completedFuture(ResultadoPagamento.emProcessamento(pedido.getId()));
}
Enter fullscreen mode Exit fullscreen mode

A injeção de falha

Usamos o Chaos Monkey for Spring Boot no payment-service, ativando o ataque de latência apenas quando o experimento começa:

# application-chaos.yml do payment-service
chaos:
  monkey:
    enabled: true
    watcher:
      rest-controller: true
    assaults:
      level: 1                  # afeta todas as requisições do ambiente do experimento
      latency-active: true
      latency-range-start: 2000
      latency-range-end: 3000
Enter fullscreen mode Exit fullscreen mode

E o controle em tempo de execução, sem redeploy, pelos endpoints do Actuator:

# iniciar o experimento
curl -X POST http://payment-canary:8080/actuator/chaosmonkey/enable
# abortar imediatamente
curl -X POST http://payment-canary:8080/actuator/chaosmonkey/disable
Enter fullscreen mode Exit fullscreen mode

O Blast Radius

Definir o raio de explosão foi a parte mais importante do planejamento:

Dimensão Limite adotado
Ambiente 1º rodada em staging; 2º rodada em produção
Tráfego afetado Apenas 5% (tarefas canary do payment-service recebendo 5% das requisições via peso no ALB)
Duração 10 minutos, com monitoramento ativo
Horário Janela de baixo movimento, equipe de prontidão presente
Usuários Contas de teste primeiro; depois clientes reais, dentro dos 5%
Aborto automático Erro do checkout > 5% por 1 min ou p95 > 3 s por 2 min
Fora do escopo Banco de dados, inventory-service, qualquer outra dependência

Regra de ouro: o raio de explosão deve ser pequeno o bastante para que o pior cenário seja aceitável, e a hipótese, grande o bastante para que valha a pena testar.

4. Observabilidade e Resultados

Não existe Chaos Engineering sem observabilidade. Se você não consegue ver o efeito da falha, o experimento não ensina nada. Montamos um painel único (Grafana + CloudWatch + tracing com OpenTelemetry) com os Quatro Sinais Dourados:

  • Latência: p50, p95 e p99 do checkout e do payment-service.
  • Tráfego: requisições por segundo.
  • Erros: taxa de 5xx e de fallback acionado.
  • Saturação: threads ativas, conexões, profundidade da fila SQS.

Além deles, métricas de negócio, como pedidos concluídos por minuto, e o estado do circuit breaker, exposto via Actuator e Micrometer (resilience4j_circuitbreaker_state).

Rodada 1 (staging): a hipótese falhou, e foi ótimo

A primeira execução refutou nossa hipótese. O que observamos:

  1. A latência do payment-service subiu, e o p95 do checkout foi para ~3,4 s.
  2. O circuit breaker demorou ~70 s para abrir, não os 30 s esperados.
  3. Motivo: o timeout de 1,5 s estava correto, mas o retry dobrava cada chamada lenta, mantendo as threads ocupadas e atrasando o acúmulo de falhas necessário para abrir o circuito.
  4. O thread pool do checkout chegou perto da saturação.

Descoberta valiosa: o retry, pensado para ajudar, estava amplificando o problema. Esse tipo de interação não aparece em teste unitário.

Correções aplicadas:

  • Retry apenas para erros de conexão, não para timeouts e chamadas lentas;
  • sliding-window-size reduzido de 20 para 10, para o circuito reagir mais rápido;
  • Bulkhead dedicado às chamadas de pagamento, isolando o restante do checkout.

Rodada 2 (produção, 5% do tráfego): a arquitetura sobreviveu

Após os ajustes, repetimos a carta, agora em produção com blast radius reduzido:

Métrica Estado estável Durante o experimento
p95 do checkout ~600 ms pico de ~1,1 s, estabilizando em ~700 ms
Taxa de erro do checkout < 1% ~0,8%
Tempo até o circuit breaker abrir n/a ~20 s
Pedidos aceitos como "em processamento" 0 ~100% dos pedidos do canary durante o circuito aberto
Profundidade da fila SQS ~0 subiu e drenou em ~4 min após o rollback
Pedidos concluídos por minuto (global) linha de base variação dentro de ±3%

Os números acima ilustram o tipo de evidência coletada no exercício; em um artigo real, substitua-os pelas medições do seu ambiente.

Padrões de resiliência que entraram em ação:

  • Timeout (TimeLimiter): impediu que chamadas lentas segurassem threads indefinidamente;
  • Circuit Breaker: parou de enviar tráfego a um serviço degradado, dando-lhe tempo de se recuperar;
  • Fallback automático: o pedido foi aceito e a cobrança, enfileirada no SQS, preservando a experiência e a receita;
  • Bulkhead: isolou o impacto, sem contaminar o restante do checkout;
  • Auto Scaling: reagiu à saturação do pool de conexões adicionando tarefas ao serviço.

Ao desativar o ataque, o circuito passou por half-open, validou 5 chamadas saudáveis e voltou a closed, e a fila foi drenada sem perda de pedidos.

Próximas cartas

O aprendizado gerou novas cartas para o baralho: COMP-012 "Queda de AZ" (derrubar todas as tarefas de uma zona de disponibilidade), DATA-003 "Failover do RDS" (forçar o failover Multi-AZ) e DEP-002 "Gateway Instável" (erros 5xx intermitentes no provedor de pagamento). Para essas, o AWS Fault Injection Service (FIS) é um bom aliado, pois permite parar instâncias, interromper tarefas e degradar rede com stop conditions ligadas a alarmes do CloudWatch.

5. Conclusão

Os experimentos com o Master Deck nos deixaram três lições principais:

  1. Resiliência não é suposição, é evidência. Nossa configuração de circuit breaker estava "certa no papel" e só falhou quando foi testada. Sem a carta, teríamos descoberto o problema do retry em um incidente real.
  2. Observabilidade vem primeiro. Sem métricas, traces e um estado estável bem definido, não há como saber se o experimento deu certo.
  3. Comece pequeno e cresça. Hipótese clara, blast radius limitado e condições de aborto transformam o caos em ciência.

O impacto na cultura DevOps

O maior ganho não foi técnico. Mudou a cultura:

  • Falhas deixaram de ser motivo de culpa e viraram fonte de aprendizado (blameless);
  • Desenvolvedores passaram a pensar em modos de falha durante o design, não só depois;
  • Post-mortems viram cartas novas, fechando o ciclo entre incidente real e prevenção;
  • Os Game Days aproximaram Dev, Ops e Produto em torno de um objetivo comum: um sistema que falha bem.

Se você quer começar amanhã: escolha uma dependência crítica, escreva uma hipótese, injete uma falha pequena em staging e observe. Depois escreva a carta e guarde no repositório. Esse é o primeiro naipe do seu baralho.

Quebre as coisas com cuidado. O seu sistema (e os seus usuários) agradecem.


Referências para aprofundar

  • Principles of Chaos Engineering: principlesofchaos.org
  • Documentação do Chaos Monkey for Spring Boot (codecentric)
  • Documentação do Resilience4j
  • AWS Fault Injection Service: documentação oficial da AWS
  • Chaos Engineering, Casey Rosenthal e Nora Jones (O'Reilly)

Gostou? Deixe nos comentários qual seria a primeira carta do seu baralho.

Tags: #chaosengineering #resilience #devops #springboot #aws #microservices

Top comments (0)