O retry que derrubou o sistema: a importância de circuit breaker, backoff e token bucket na prática
O serviço começa a ficar lento. Os timeouts aparecem. Os clientes fazem o que todo cliente bem-intencionado faz: tentam de novo. A fila cresce, o tempo de resposta piora, e o tempo de resposta pior gera... mais retry.
Alguém grita "sobe mais duas réplicas!". E... o sistema cai de novo.
Se você já viveu isso, você já tomou um retry storm: a tempestade de retentativas que transforma um serviço lento em um serviço morto.
Por que subir réplica não resolve
Quando o throughput encosta no limite, nasce um ciclo vicioso: fila longa de requisições → tempo de resposta maior → mais timeouts → mais retentativas → fila ainda maior.
O detalhe cruel é que reiniciar ou escalar o serviço não quebra o ciclo. A carga não foi embora: ela está lá, enfileirada, esperando. O serviço volta e é sobrecarregado de novo, em instantes. É por isso que a resposta para sobrecarga quase nunca é "mais máquina", e sim controlar o fluxo.
O arsenal contra a sobrecarga
Circuit breaker: pare de insistir. O disjuntor abre, parte das requisições é recusada na entrada, e o serviço ganha espaço para recuperar a saúde aos poucos. Melhor responder erro rápido para alguns do que travar para todos.
Exponential backoff: se a primeira tentativa falhou, esperar o mesmo 1 segundo e tentar de novo só afunda mais o sistema. Dobre o intervalo (1s, 2s, 4s, 8s) ou use tempos aleatórios (jitter), para os clientes não voltarem todos juntos na mesma onda.
Token bucket (rate limiting): pense num balde de fichas por usuário. Cada requisição consome uma ficha; encheu o limite, espera um pouco. Distribui a carga entre os usuários em vez de deixar um só monopolizar o sistema.
Load shedding: o sistema detecta a sobrecarga se aproximando e passa a rejeitar requisições de forma proativa, no estilo "eu sei que não vou conseguir atender isso agora".
Back pressure: em vez de só rejeitar, o sistema avisa o cliente que precisa diminuir a velocidade de envio.
Plot twist: o TCP faz isso desde os anos 80 com maestria
A parte que mais me diverte nessa discussão: tudo isso já existe dentro do TCP. Controle de fluxo, controle de congestionamento, retentativa com recuo. Está tudo embutido no protocolo em que a internet inteira está calcada, decidido lá atrás nos anos 80/90. A gente redescobre na camada de aplicação o que a camada de transporte já resolvia sem ninguém perceber.
O princípio por trás de tudo
Esses mecanismos compartilham uma filosofia: prefira um fault a um failure. Você sabe a diferença?
Fault é o problema que o sistema contorna: recusa uma requisição, degrada um pedaço, vai capengando, mas continua funcionando e preservando os fluxos críticos. Failure é quando o sistema não lida bem com algum problema e cai de vez (503).
As técnicas citadas acima fazem isso: geram um fault de propósito, um erro controlado agora, uma leve degradação, para evitar o failure total do sistema.
Resiliência não é impedir todo erro; é escolher quais erros você vai tolerar para continuar de pé.
Esse papo saiu do nosso clube do livro, em que estamos lendo o Designing Data-Intensive Applications.
A segunda parte do Capítulo 2 é hoje, fica aqui nosso convite a você:
https://craftcodeclub.io/book-club/designing-data-intensive-applications
E vocês, já tomaram um retry storm em produção? Como seguraram a onda? Compartilhem a experiência nos comentários.