Por que sua implementação (provavelmente) está errada e como o "Jitter" salva seu sistema
Em arquitetura de sistemas distribuídos, uma regra é clara: a rede vai falhar. APIs vão ficar lentas. Serviços vão cair. Isso não é uma possibilidade, é uma certeza. São as falhas transitórias.
Qual a primeira solução que vem à mente para lidar com isso? Um Retry Pattern. Se a chamada falhar, tente de novo. Simples, certo?
Quase. Na verdade, uma implementação ingênua de retry não apenas não resolve o problema, como pode ser a causa de uma falha em cascata que derruba seu sistema inteiro.
O Problema: O "Estouro da Manada" (Thundering Herd)
Vamos imaginar um cenário:
O Serviço-B (ex: um serviço de pagamento) fica lento. O Serviço-A (ex: um e-commerce), que consome o Serviço-B, tem 100 instâncias rodando.
Um cliente tenta pagar. A chamada do Serviço-A para o Serviço-B dá timeout.
O Serviço-A tem um retry simples: "Falhou? Tente de novo imediatamente."
Todas as 100 instâncias do Serviço-A começam a bombardear o Serviço-B com retries imediatos para milhares de requisições.
Resultado: Você acabou de criar um ataque de negação de serviço (DDoS) contra seu próprio sistema. O Serviço-B, que estava apenas lento, agora está morto. Isso é o "Estouro da Manada".
A Solução Intermediária: Exponential Backoff
Ok, então retries imediatos são ruins. A próxima solução lógica é o Exponential Backoff: esperar antes de tentar de novo, e aumentar essa espera a cada falha.
Falha 1: Espere 1 segundo.
Falha 2: Espere 2 segundos.
Falha 3: Espere 4 segundos.
Falha 4: Espere 8 segundos.
Isso é muito melhor. Damos tempo para o Serviço-B respirar e se recuperar.
Mas ainda temos um problema, mais sutil. Se as 100 instâncias do Serviço-A falharem ao mesmo tempo, elas vão esperar 1 segundo e tentar de novo... ao mesmo tempo. Depois, vão esperar 2 segundos e tentar de novo... ao mesmo tempo.
Você não eliminou o estouro da manada; você apenas o agendou. Você criou picos de carga sincronizados que ainda podem sobrecarregar o Serviço-B.
A Solução Sênior: Exponential Backoff + Jitter
Aqui está o pulo do gato que separa uma arquitetura robusta de uma frágil: Jitter (tremulação, ou aleatoriedade).
A ideia é simples: adicione um pequeno fator aleatório ao seu tempo de espera. Em vez de todas as instâncias esperarem exatamente 4 segundos, elas esperarão entre 3 e 5 segundos (por exemplo).
O Jitter "espalha" as novas tentativas no tempo, quebrando aquele pico de carga sincronizado e transformando-o em uma carga distribuída e muito mais gerenciável para o serviço dependente.
Um exemplo de implementação (Pseudocódigo/C#):
// Fórmula básica de Exponential Backoffint baseDelayMs = 1000; // 1 segundo
int maxRetries = 5;
var random = new Random();
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
// 1. Tenta a operação
await CallMyService();
return; // Sucesso!
}
catch (TransientException ex)
{
// 2. Falhou? Calcula o backoff
int backoffMs = (int)Math.Pow(2, attempt) * baseDelayMs;
// 3. Adiciona Jitter
// (ex: adiciona um valor aleatório entre 0ms e 500ms)
int jitterMs = random.Next(0, 500);
int totalDelay = backoffMs + jitterMs;
await Task.Delay(totalDelay);
}
}
// Se chegou aqui, todas as tentativas falharam.
throw new Exception("Serviço indisponível após N tentativas.");
Conclusão
Não basta apenas re-tentar. Em sistemas distribuídos, é preciso re-tentar de forma inteligente.
Uma estratégia de retry sem Exponential Backoff é perigosa. Uma estratégia de Exponential Backoff sem Jitter é incompleta. Ao combinar os três, você garante que seus mecanismos de resiliência não se tornem a causa da sua próxima outage.