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

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

@mjovanovictech

Como testar resiliência de sistemas em produção real

Ver post completo no X →
@mjovanovictech

Padrões de resiliência em .NET Core com exemplos

Ver post completo no X →
@mjovanovictech

Arquitetura de software orientada a domínio

Ver post completo no X →
@mjovanovictech

Lições de 5 anos mantendo sistemas em produção

Ver post completo no X →
@mjovanovictech

Design de APIs resilientes para produção

Ver post completo no X →
@mjovanovictech

Microsserviços vs monolito — como escolher

Ver post completo no X →

O que dizem

Henrique M. ★★★★★

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.

Vanessa K. ★★★★★

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.

Carlos E. ★★★★☆

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.