DEV Community

bruno candido salles
bruno candido salles

Posted on

Quebrei meu próprio sistema de propósito (e foi a melhor decisão da sprint)

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:

  1. Hipótese: o que esperamos que aconteça.
  2. Alvo e variável: o que será perturbado e como (tipo de falha, intensidade, duração).
  3. Estado estável: as métricas de controle e seus limites.
  4. Blast radius: quem e o que pode ser afetado.
  5. Condições de aborto: quando o experimento é interrompido.
  6. Observabilidade: onde olhar durante a execução.
  7. 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
                 └──────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
@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
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Falhas lentas são traiçoeiras. Teste latência, não só queda total.
  2. Hipótese antes de ação. Uma carta preenchida obriga a pensar no que se espera e no que fazer se der errado.
  3. Blast radius pequeno primeiro. Confiança se constrói em degraus.
  4. Observabilidade é pré-requisito. Sem ela, o experimento não ensina nada.
  5. 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)