Por que retry é necessário mas perigoso sem controle
Falhas temporárias são normais em produção — a questão é como responder
Em sistemas distribuídos, falhas temporárias são inevitáveis: um serviço dependente está reiniciando, a rede teve um spike de latência, o banco de dados estava sob pico de carga por um instante. Retry automático é a resposta natural — tentar de novo após uma falha temporária é correto e necessário. O perigo é retry sem controle: se 1000 clientes recebem erro e todos tentam de novo imediatamente, o serviço que estava se recuperando recebe 1000 requisições simultâneas e colapsa de vez. Retry sem backoff não é resiliência — é um mecanismo que transforma problemas temporários em permanentes.
Exponential Backoff — aumentar o tempo de espera progressivamente
Dobrar o intervalo de espera a cada tentativa frustrada
Exponential backoff é o algoritmo padrão para retry responsável: em vez de tentar de novo imediatamente, o cliente aguarda um intervalo que dobra a cada tentativa. Primeira tentativa: imediata. Segundo retry: aguardar 1 segundo. Terceiro: 2 segundos. Quarto: 4 segundos. Quinto: 8 segundos. Com esse padrão, um cliente não bombardeia o servidor com retries agressivos enquanto ele está se recuperando. Ao mesmo tempo, tenta regularmente até obter sucesso. A maioria das implementações também define um limite máximo de espera (como 60 segundos) para evitar que o cliente espere indefinidamente antes de cada tentativa.
Jitter — adicionando aleatoriedade para evitar sincronização
Sem jitter, todos os clientes tentam ao mesmo tempo após o backoff
O problema do exponential backoff puro é a sincronização: se 500 clientes começaram a fazer retry ao mesmo tempo (por exemplo, após um restart do servidor), todos vão esperar exatamente 1 segundo, depois exatamente 2 segundos, depois exatamente 4 — e cada vez que o intervalo expira, 500 requisições chegam simultaneamente. Jitter resolve isso adicionando um componente aleatório ao intervalo de espera: em vez de aguardar exatamente 4 segundos, o cliente aguarda entre 3 e 5 segundos (ou entre 0 e 4 segundos no full jitter). Isso distribui as tentativas no tempo, reduzindo drasticamente os picos de carga em recuperação.
Quantas vezes tentar — definindo o número máximo de retries
Tentar para sempre é tão ruim quanto nunca tentar
O número de retries deve ser limitado. Tentar infinitamente consome recursos (threads, conexões, memória de fila) e pode mascarar problemas reais que precisam de atenção humana. A prática comum é entre 3 e 5 retries para operações síncronas (o usuário está esperando), e mais retries para jobs assíncronos em background onde a latência é tolerada. Após atingir o número máximo de tentativas, o sistema deve: enviar o job para uma Dead Letter Queue para análise posterior, registrar o erro com contexto suficiente para diagnóstico, notificar alertas se o volume de falhas for significativo, e retornar erro claro para o chamador.
Quais erros devem ser repetidos e quais não
Erros temporários versus erros permanentes
Não todo erro merece retry. Erros temporários que justificam retry: 503 Service Unavailable, 429 Too Many Requests (com Retry-After), 504 Gateway Timeout, erros de rede como connection reset. Erros permanentes que nunca devem ser repetidos: 400 Bad Request (os dados do request estão errados — retry com o mesmo payload vai falhar de novo), 401 Unauthorized (sem retry, precisa de nova autenticação), 403 Forbidden (sem retry), 404 Not Found para recursos que não vão aparecer. Para 429, o retry deve respeitar o header Retry-After do servidor em vez de aplicar backoff próprio. Para erros de rede não categorizado, 3 retries com exponential backoff é o padrão conservador.
Circuit Breaker — parar de tentar quando o serviço claramente está com problema
Falha rápido em vez de esperar timeout de cada requisição
Circuit breaker é um padrão complementar ao retry: monitora a taxa de falhas de chamadas a um serviço e, quando ultrapassa um threshold, "abre" o circuito — parar de fazer chamadas ao serviço com problema e retornar erro imediatamente, sem esperar timeout. Isso tem dois benefícios: libera recursos do cliente (sem esperar 30 segundos de timeout por requisição) e dá espaço ao serviço com problema para se recuperar sem ser bombardeado por novas chamadas. Após um período de espera, o circuit breaker testa com uma chamada — se funcionar, fecha o circuito; se falhar, mantém aberto. Bibliotecas como Polly (.NET), Resilience4j (Java) e hystrix implementam este padrão.
Retry em filas de mensagens — o at-least-once e a dead letter queue
Como filas como SQS e RabbitMQ gerenciam retries automaticamente
Filas de mensagens com garantia "ao menos uma vez" já têm retry embutido: se o consumer não confirmar o processamento dentro do visibility timeout, a mensagem é reentregue para outro consumer. Para controlar quantas vezes uma mensagem é reentregada, configure maxReceiveCount (SQS) ou x-max-delivery-count (RabbitMQ). Mensagens que atingem o limite são movidas para a Dead Letter Queue, onde podem ser inspecionadas, corrigidas e reprocessadas manualmente. A combinação de retry automático da fila + Dead Letter Queue é o padrão mais robusto para workers assíncronos — garante que mensagens com problemas temporários sejam processadas e mensagens com problemas permanentes não fiquem em loop infinito.
Idempotência como pré-requisito para retry seguro
Retry sem idempotência duplica operações
Retry e idempotência são inseparáveis. Se a operação que está sendo repetida não for idempotente, cada retry cria um novo efeito: pagamentos duplicados, emails reenviados, pedidos duplicados. Antes de implementar retry em qualquer operação crítica, a operação deve ser idempotente — garantindo que executar N vezes produz o mesmo resultado que executar uma vez. Para operações que não são naturalmente idempotentes (como POST de criação), Idempotency Keys tornam-nas seguras para retry. Sem idempotência, retry é mais perigoso que não tentar de novo.
Monitoramento de retries em produção
Métricas que revelam se retries estão funcionando como esperado
Métricas essenciais para monitorar retries: taxa de retry por operação (quantas requisições precisaram de pelo menos um retry), taxa de sucesso em retries (quantas operações que falharam na primeira tentativa tiveram sucesso no retry — deve ser alta para falhas temporárias), volume de Dead Letter Queue (crescimento indica problemas recorrentes que precisam de atenção), e latência p99 (retries aumentam latência — monitorar para detectar quando backoff está muito longo). Alertas em aumento súbito da taxa de retry são excelentes indicadores de problemas emergentes nos serviços dependentes.
Conclusão — retry com backoff é resiliência responsável
A diferença entre se recuperar de falhas temporárias e amplificar problemas
Retry sem controle é pior do que não tentar de novo. Com exponential backoff, jitter e circuit breaker, retry se torna um mecanismo de resiliência genuíno que permite que sistemas se recuperem de falhas temporárias sem agravar a situação. A regra fundamental: nunca faça retry imediato em loop, nunca tente para sempre, e certifique-se de que a operação sendo repetida é idempotente. Continue em: Fundamentos obrigatórios antes de produção.
Retry, Backoff e Resiliência — Vídeos
Idempotency Explained — pré-requisito para retry seguro
Idempotency in APIs — como retry sem idempotência duplica operações
One Serverless Principle to Rule Them All: Idempotency
Idempotency in APIs: you should be aware of this!
The Complexity Iceberg — o que se esconde por baixo de retry em produção
Kafka In 3 Minutes — filas e retries em mensageria
Conceitos-chave
Exponential Backoff
Algoritmo que dobra o intervalo de espera a cada retry: 1s, 2s, 4s, 8s — dá tempo ao serviço de se recuperar.
Jitter
Componente aleatório adicionado ao backoff para evitar que todos os clientes tentem ao mesmo tempo.
Circuit Breaker
Abre o circuito após muitas falhas consecutivas, retornando erro imediato sem esperar timeout — protege serviços em recuperação.
Dead Letter Queue
Fila para mensagens que falharam mais vezes que o máximo configurado — para análise e reprocessamento manual.
maxReceiveCount
Configuração do SQS que define quantas vezes uma mensagem pode ser entregue antes de ir para a DLQ.
Retry-After
Header HTTP enviado pelo servidor em respostas 429/503 indicando quando o cliente pode tentar novamente.
Sistemas Distribuídos no Instagram
@bytebytego
Reels — Arquitetura e Backend
@bytebytego
ByteByteGo no Facebook
Sistemas em Produção no X
Links Úteis
O que dizem
O jitter foi o que faltava na nossa implementação de retry. Tínhamos exponential backoff mas sem jitter, e em incidentes todos os serviços se sincronizavam e derrubavam o banco de vez quando ele voltava. Depois do jitter, a recuperação ficou muito mais suave.
Excelente ponto sobre quais erros não devem receber retry. Tínhamos retry em 404 e 400 porque nossa lib de retry era genérica demais. Resultado: loops infinitos consumindo recursos sem chance de sucesso. Filtrar pelo código HTTP é obrigatório.
Bom artigo. Complementando: o Polly (.NET) tem implementação pronta de retry com exponential backoff + jitter + circuit breaker que funciona muito bem. Para quem usa .NET, é a primeira escolha. Resilience4j faz o mesmo para Java/Kotlin.