System Design/03 - Qualidade/Performance Antipatterns2 min
Retry Storm
- Que problema isso resolve?
- Quando usar / quando NÃO usar?
- Qual o principal trade-off?
- Como isso falha em produção?
Conceito
A reação à falha amplifica a falha. Um serviço degrada, os clientes repetem, a carga aumenta, o serviço piora — e o ciclo se realimenta.
serviço lento → clientes repetem → 3x mais carga
→ mais lento ainda → mais repetições
→ colapso
O agravante é a multiplicação em cascata: se cada camada repete três vezes, quatro camadas encadeadas produzem 3⁴ = 81 requisições para uma única chamada original.
O efeito rebanho
Retry com intervalo fixo sincroniza os clientes: todos falham juntos, todos esperam o mesmo tempo, todos repetem no mesmo instante. O serviço recebe rajadas periódicas em vez de carga distribuída — e nunca consegue se recuperar entre elas.
É o mesmo fenômeno que impede a recuperação após uma queda: no momento em que o serviço volta, todos os clientes acumulados tentam ao mesmo tempo e o derrubam de novo.
As defesas
| Defesa | Efeito |
|---|---|
| Backoff exponencial | Espaça as tentativas: 1 s, 2 s, 4 s, 8 s |
| Jitter | Dessincroniza os clientes — essencial |
| Limite de tentativas | Impede repetição infinita |
| Circuit Breaker | Para de tentar quando é claro que não adianta |
| Retry budget | Teto global: no máximo 10% do tráfego pode ser retry |
| Retry só na borda | Evita a multiplicação em cascata |
| Não repetir 4xx | Erro do cliente não melhora com repetição |
Trade-offs
Jitter é a defesa mais importante e a mais esquecida. Backoff exponencial sem jitter continua sincronizado: todos os clientes esperam 1 s, depois 2 s, depois 4 s — juntos. O gráfico de carga vira uma série de picos, e o serviço não se recupera entre eles.
O trade-off do retry em si é entre resiliência e amplificação: repetir aumenta a chance de sucesso numa falha transitória e piora qualquer falha sistêmica. Como distinguir uma da outra em tempo real é difícil, a prática segura é limitar o orçamento total de retry — assim, mesmo diagnosticando errado, o dano é limitado.
A recomendação de repetir apenas na camada mais externa resolve a multiplicação: camadas internas falham rápido e propagam, e uma única camada decide se vale tentar de novo.
E vale lembrar que retry só é seguro em operação idempotente (Idempotent Operations). Repetir uma cobrança não idempotente cobra duas vezes.
Exemplo prático
# amplifica: sincronizado, sem teto, sem circuit breaker
for tentativa in range(5):
try: return chamar()
except: time.sleep(1)
# seguro: exponencial, com jitter, limitado
for tentativa in range(3):
try: return breaker.chamar(operacao)
except TransitorioError:
if tentativa == 2: raise
espera = min(2 ** tentativa, 30) * random.uniform(0.5, 1.5)
time.sleep(espera)
Três diferenças importam. O backoff exponencial dá tempo real de recuperação. O jitter (uniform(0.5, 1.5)) espalha as tentativas de mil clientes por uma janela em vez de concentrá-las num instante. E o circuit breaker interrompe a insistência quando ficou claro que o serviço está fora — convertendo espera em falha rápida.
Relacionado
Parte de Performance Antipatterns · roadmap.sh/system-design