Thundering herd: o que é, o caso do Slack e como prevenir
Estudei sobre thundering herd: o incidente do Slack, por que não é só 'muito tráfego' e as três técnicas que evitam a sincronização em massa: backoff com jitter, circuit breaker e load shedding.
Recentemente foquei em estudar sobre thundering herd, principalmente como ocorreu no Slack, como prevenir que aconteça e por que ele é mais traiçeiro do que parece. Resolvi documentar aqui o que aprendi.
O que é thundering herd?
Thundering herd (literalmente “manada trovejante”) é quando muitos clientes ou processos ficam sincronizados e “acordam” ao mesmo tempo para bater em um mesmo recurso.
O ponto-chave: não é só muito tráfego. É muito tráfego no MESMO momento.
Um sistema pode aguentar 2.000 requisições por segundo distribuídas no tempo e morrer com as mesmas 2.000 requisições concentradas em 50 milissegundos. A diferença entre os dois cenários não está no volume, está na sincronização.
Foi exatamente isso que aconteceu no Slack: cerca de 2,3 milhões de queries foram executadas no MESMO momento. Cada query isolada era barata. Juntas e simultâneas, derrubaram o serviço.
Onde isso aparece no dia a dia
O que me chamou atenção ao estudar é que a sincronização nasce de lugares comuns:
- Cache expirando junto: milhares de chaves com o mesmo TTL vencem no mesmo instante e todas as requisições de miss vão bater no banco juntas (o famoso cache stampede)
- Retry em massa: um serviço dá erro por 2 segundos e todos os clientes que falharam retornam juntos depois do mesmo timeout
- Crons idênticos: vários agendamentos configurados “às 00:00” disparam na mesma hora
- Reconexão após restart/deploy: o serviço cai, todos os clientes detectam e reconectam no mesmo instante em que ele volta
Esse último ponto me tocou de perto. Trabalho com pipelines disparados por Scheduler: múltiplos agendamentos independentes que, se mal configurados, viram uma manada pontual de jobs. A fila absorve parte do problema, mas a origem da sincronização sou eu que criei.
Como prevenir
1. Backoff exponencial com jitter
Em vez de todos os clientes tentarem de novo ao mesmo tempo quando ocorre um erro, cada tentativa de retry espera um tempo crescente (exponencial) somado a um fator aleatório, o jitter.
const attempt = 3;
const baseMs = 1000;
const jitter = Math.random() * 500;
const delay = Math.pow(2, attempt) * baseMs + jitter;
// 1s → 2s → 4s → 8s → ... + um pequeno valor aleatório
O backoff sozinho ainda sincroniza: se todos falharam juntos e usam a mesma sequência, voltam juntos em cada rodada. Quem quebra a sincronização é o jitter, que espalha as requisições no tempo.
Resultado: menos picos, mais estabilidade.
2. Circuit breaker
Um “disjuntor” entre o cliente e o serviço. Quando o sistema percebe que um serviço está falhando demais, ele abre o circuito e para de enviar requisições temporariamente.
Os estados básicos:
- Fechado: tudo funciona normal, requisições passam
- Aberto: falhou demais, chamadas são bloqueadas imediatamente
- Meio-aberto: testa poucas requisições antes de liberar tudo de novo
Isso evita que um serviço já sobrecarregado seja ainda mais pressionado por retries. A lógica que fica é:
Falhar rápido é muito mais barato do que falhar devagar.
3. Load shedding
Basicamente: rejeitar parte das requisições de propósito.
Quando o sistema atinge um limite crítico, ele começa a dizer “não” para alguns usuários ou processos, em vez de tentar atender todos e cair. Exemplos práticos:
- Bloquear usuários não prioritários
- Retornar erro rápido (503) em vez de processar até estourar
- Limitar o tamanho das filas
- Derrubar primeiro as requisições mais pesadas
Parece contraintuitivo, mas a conta é simples: é melhor perder uma parte do tráfego do que perder tudo.
Bônus: não crie a manada em primeiro lugar
As três técnicas acima tratam a manada depois que ela existe. Mas a prevenção mais barata é na origem:
- TTL de cache com jitter para as chaves não vencerem juntas
- Offsets aleatórios em crons que não precisam rodar no mesmo minuto
- Restarts/deploy escalonados por lote de instâncias, em vez de derrubar tudo e subir tudo
Se os clientes nunca se sincronizam, não há manada para dissipar.
Resumo do que aprendi
| Técnica | O que faz | Quando usar |
|---|---|---|
| Backoff + jitter | Espalha retries no tempo | Todo cliente que faz retry |
| Circuit breaker | Para de bater em serviço doente | Entre serviços que se chamam |
| Load shedding | Rejeita tráfego de propósito | Na borda de sistemas sob pressão |
| Prevenção na origem | Evita a sincronização | TTLs, crons e deploys |
Thundering herd me ensinou uma coisa que vale além do tema: em sistemas distribuídos, o inimigo raramente é o volume. É a coordenação acidental, milhares de decisões individuais e racionais que, somadas, viram uma manada.
Se você curtiu o tema, vale estudar junto: cache stampede, retry storm e singleflight/request coalescing são variações do mesmo problema.