Retry
O problema no contexto distribuído
Seção intitulada “O problema no contexto distribuído”Em sistemas distribuídos, falhas parciais e transitórias são a norma: um pod reiniciando, um pico de latência, um rebalanceamento de partição, um 429/503 momentâneo. Falhar imediatamente na primeira tentativa desperdiça requisições que teriam sucesso segundos depois. Por outro lado, repetir cegamente pode piorar um incidente (retry storm) e corromper estado se a operação não for idempotente.
A solução
Seção intitulada “A solução”Envolver a chamada em uma política que, ao capturar uma exceção classificada como transitória, aguarda e tenta de novo, até um limite de tentativas.
Backoff exponencial
Seção intitulada “Backoff exponencial”O intervalo entre tentativas cresce exponencialmente: base * 2^(n-1). Ex.: 200ms, 400ms, 800ms, 1600ms. Evita marteladas em intervalo fixo e dá tempo para a dependência se recuperar.
Jitter (aleatoriedade)
Seção intitulada “Jitter (aleatoriedade)”Sem jitter, muitos clientes que falharam ao mesmo tempo tentam de novo no mesmo instante — o efeito thundering herd, que ressincroniza a carga e mantém a dependência sobrecarregada. O jitter espalha as tentativas adicionando aleatoriedade ao intervalo (full jitter: random(0, base * 2^(n-1))).
Classificação de falhas
Seção intitulada “Classificação de falhas”Só repita o transitório:
- Repetir: timeouts de conexão,
503 Service Unavailable,429 Too Many Requests(respeitandoRetry-After), erros de rede. - NÃO repetir:
400 Bad Request,401/403,404,422— erros determinísticos que falharão igual em toda tentativa. Repetir é só desperdício e latência.
Idempotência é pré-requisito
Seção intitulada “Idempotência é pré-requisito”Se a primeira tentativa chegou ao servidor e teve efeito, mas a resposta se perdeu, o retry repete o efeito. Para operações não idempotentes (ex.: “debitar R$10”), isso duplica o efeito. Torne a operação idempotente com chave de idempotência (o servidor deduplica pela chave) ou use apenas em GET/PUT/DELETE naturalmente idempotentes.
Trade-offs
Seção intitulada “Trade-offs”- Prós: absorve falhas transitórias de forma transparente, melhora a taxa de sucesso efetiva.
- Contras: aumenta a latência (cada tentativa espera), pode amplificar sobrecarga (retry storm) e é perigoso sem idempotência. Retries também consomem threads/conexões enquanto esperam.
Quando NÃO usar
Seção intitulada “Quando NÃO usar”- Em falhas não transitórias (erros de validação, autorização) — repetir não muda o resultado.
- Em operações não idempotentes sem chave de idempotência.
- Quando a latência adicional é inaceitável para o SLA da chamada.
- Quando o Circuit Breaker já está aberto — não faz sentido tentar contra uma dependência conhecidamente morta.
Exemplo Java/Spring com Resilience4j
Seção intitulada “Exemplo Java/Spring com Resilience4j”@Configurationpublic class RetryBeansConfig {
@Bean public RetryRegistry retryRegistry() { var config = RetryConfig.custom() .maxAttempts(4) .intervalFunction(IntervalFunction .ofExponentialRandomBackoff( Duration.ofMillis(200), 2.0, 0.5)) .retryOnException(ex -> ex instanceof CallNotPermittedException == false && isTransient(ex)) .build(); return RetryRegistry.of(config); }
private boolean isTransient(Throwable ex) { if (ex instanceof HttpServerErrorException server) { return server.getStatusCode().is5xxServerError(); } if (ex instanceof HttpClientErrorException.TooManyRequests) { return true; } return ex instanceof ResourceAccessException; }}
@Service@RequiredArgsConstructorpublic class PricingGateway {
private final RestClient restClient;
@Retry(name = "pricing") public Price fetchPrice(String sku) { return restClient.get() .uri("/prices/{sku}", sku) .retrieve() .body(Price.class); }}ofExponentialRandomBackoff(200ms, 2.0, 0.5) = intervalo base 200ms, multiplicador 2, com 50% de jitter. A anotação @Retry(name = "pricing") aplica a política; costuma ser composta por fora de um @CircuitBreaker de mesmo nome.
graph TD
A[Chamada remota] --> B{Sucesso?}
B -- sim --> Z[Retorna resultado]
B -- não --> C{Falha transitória?}
C -- não --> E[Propaga erro imediatamente]
C -- sim --> D{Tentativas < max?}
D -- não --> F[Esgotou: propaga / Fallback]
D -- sim --> G[Espera backoff + jitter]
G --> A
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Retry sem backoff (loop imediato): martela a dependência e vira DoS acidental.
- Retry sem jitter: thundering herd — todos os clientes tentam no mesmo instante.
- Repetir erros determinísticos (
4xx): latência e carga sem chance de sucesso. - Retry em operação não idempotente: efeitos duplicados (cobrança em dobro).
- Retries aninhados em várias camadas (client + gateway + service): multiplicam-se —
4 x 4 x 4tentativas. Defina o retry em uma camada. - Retry sem Circuit Breaker: continua tentando contra uma dependência morta, prolongando o incidente.
Relações
Seção intitulada “Relações”- Combina com Circuit Breaker (não tentar quando aberto) e Fallback (resposta quando esgota).
- Frequentemente aplicado a chamadas de API Composition e a passos de SAGA.
- Contexto: APIs - REST, GraphQL e gRPC, Microservices.
- Índice: Microservices Patterns.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”- Por que idempotência é pré-requisito para retry seguro?
Resposta
Porque uma tentativa pode ter chegado e produzido efeito no servidor, mas a resposta se perdeu (timeout). O retry então repete a operação. Se ela não for idempotente (ex.: debitar valor), o efeito é aplicado duas vezes. Idempotência — nativa ou via chave de idempotência que o servidor deduplica — garante que repetir não altere o resultado.
- Para que serve o jitter no backoff?
Resposta
Para evitar o thundering herd: sem aleatoriedade, todos os clientes que falharam simultaneamente tentam de novo no mesmo instante, ressincronizando a carga e mantendo a dependência sobrecarregada. O jitter espalha as tentativas no tempo adicionando um componente aleatório ao intervalo.
- Quais tipos de falha NÃO devem ser repetidos?
Resposta
Falhas determinísticas: 400 Bad Request, 401/403 (auth), 404, 422. Elas falharão igual em toda tentativa — repetir só adiciona latência e carga. Retry serve para falhas transitórias: 503, 429, timeouts e erros de rede.
- Por que retry e circuit breaker se complementam?
Resposta
O retry cobre falhas transitórias de curta duração. O Circuit Breaker detecta falhas persistentes e abre o circuito, fazendo os retries falharem rápido (ou nem executarem) em vez de martelar uma dependência morta. Sem o breaker, os retries prolongariam o incidente e consumiriam recursos inutilmente.