Retry Pattern

Como lidar com falhas transitórias em sistemas distribuídos reexecutando operações com estratégias de backoff para melhorar a resiliência.

Que problema resolve

Em sistemas distribuídos, as chamadas entre serviços podem falhar por razões transitórias: problemas momentâneos de rede, serviços reiniciando, limites temporários de rate limiting ou picos de carga. Essas falhas são temporárias e a operação provavelmente terá sucesso se for reexecutada.

Sin Retry:
  Servicio A ──► Servicio B
                 ❌ Error 503 (sobrecarga temporal)
  
  Servicio A devuelve error al usuario
  (aunque B se recupera en 2 segundos)

O problema é que, sem retentativas, uma falha transitória de milissegundos se transforma em um erro visível para o usuário, quando simplesmente esperar um momento e tentar de novo teria resolvido o problema.

Falhas transitórias comuns

Tipo de falhaCausaDuração típica
Timeout de redeCongestionamento momentâneoMilissegundos a segundos
HTTP 503Serviço reiniciando ou sobrecarregadoSegundos
HTTP 429Rate limit atingidoSegundos a minutos
Connection refusedServiço iniciandoSegundos
DNS resolution failureCache DNS expiradoMilissegundos

Como funciona

O padrão Retry intercepta as chamadas que falharam e as reexecuta automaticamente conforme uma estratégia configurada. A chave está em quando reexecutar, quantas vezes e quanto esperar entre as tentativas.

Con Retry:
  Servicio A ──► Servicio B
                 ❌ Intento 1: Error 503
                 ⏳ Espera 1 segundo
                 ❌ Intento 2: Error 503
                 ⏳ Espera 2 segundos
                 ✅ Intento 3: OK 200
  
  El usuario recibe respuesta exitosa

Estratégias de espera (backoff)

EstratégiaFórmulaExemplo (3 tentativas)Uso
Fixed delaydelay constante1s, 1s, 1sCasos simples
Exponential backoffdelay × 2^tentativa1s, 2s, 4sPadrão recomendado
Exponential + jitterrandom(0, delay × 2^tentativa)0.7s, 1.3s, 3.8sEvita thundering herd
Linear backoffdelay × tentativa1s, 2s, 3sIncremento previsível

Exponential backoff com jitter

A estratégia mais recomendada é o exponential backoff com jitter (variação aleatória). Sem jitter, quando um serviço se recupera, todos os clientes tentam novamente ao mesmo tempo, criando um pico de carga que pode derrubá-lo de novo (thundering herd problem).

Sin jitter (thundering herd):
  Cliente 1: retry en 1s, 2s, 4s
  Cliente 2: retry en 1s, 2s, 4s    ← Todos al mismo tiempo
  Cliente 3: retry en 1s, 2s, 4s
  
  Servicio B recibe 3 requests simultáneos en cada intervalo

Con jitter:
  Cliente 1: retry en 0.7s, 1.8s, 3.2s
  Cliente 2: retry en 1.2s, 2.3s, 4.5s  ← Distribuidos en el tiempo
  Cliente 3: retry en 0.9s, 1.5s, 3.9s
  
  Servicio B recibe requests distribuidos

Parâmetros de configuração

ParâmetroDescriçãoValor típico
Max retriesNúmero máximo de retentativas3-5
Initial delayTempo de espera antes da primeira retentativa100ms - 1s
Max delayTempo máximo de espera entre retentativas30s - 60s
Backoff multiplierFator de multiplicação exponencial2
Retryable errorsCódigos de erro que justificam a retentativa408, 429, 500, 502, 503, 504

O que reexecutar e o que não

✅ Reintentar (fallos transitorios):
  - HTTP 408 Request Timeout
  - HTTP 429 Too Many Requests
  - HTTP 500 Internal Server Error (con precaución)
  - HTTP 502 Bad Gateway
  - HTTP 503 Service Unavailable
  - HTTP 504 Gateway Timeout
  - Connection timeout / Connection refused

❌ NO reintentar (fallos permanentes):
  - HTTP 400 Bad Request (datos inválidos)
  - HTTP 401 Unauthorized (credenciales incorrectas)
  - HTTP 403 Forbidden (sin permisos)
  - HTTP 404 Not Found (recurso no existe)
  - HTTP 409 Conflict (conflicto de estado)
  - HTTP 422 Unprocessable Entity (validación fallida)

Vantagens

  • Resiliência a falhas transitórias: A maioria dos erros de rede se resolve sozinha
  • Transparente para o usuário: A retentativa acontece sem que o usuário perceba
  • Implementação simples: É um dos padrões de resiliência mais fáceis de implementar
  • Configurável: Os parâmetros se ajustam conforme o contexto e a criticidade
  • Complementa outros padrões: Funciona bem com Circuit Breaker e Timeout

Trade-offs / Desvantagens

  • Latência acumulada: Cada retentativa adiciona tempo de espera total
  • Carga adicional: As retentativas geram mais tráfego para o serviço que já está falhando
  • Operações não idempotentes: Reexecutar uma operação que já foi realizada pode causar duplicações
  • Falsos positivos: Reexecutar um erro permanente desperdiça recursos
  • Thundering herd: Sem jitter, as retentativas sincronizadas podem piorar a situação
  • Complexidade de configuração: Parâmetros incorretos podem causar mais problemas do que soluções

Quando usar

  • Chamadas HTTP a serviços externos ou microsserviços
  • Operações de rede que podem falhar por razões transitórias
  • Consultas a bancos de dados que podem falhar por conexões esgotadas
  • Publicação de mensagens em brokers que podem estar temporariamente indisponíveis
  • Qualquer operação idempotente que falha de forma intermitente

Quando evitar

  • Operações não idempotentes em que reexecutar pode causar efeitos colaterais duplicados
  • Quando o erro é claramente permanente (400, 401, 403, 404)
  • Operações de longa duração em que o timeout total seria inaceitável
  • Quando você já tem um Circuit Breaker aberto (o retry não deve tentar passar por um circuito aberto)
  • Sistemas em que a latência adicional das retentativas é inaceitável

Tecnologias e implementações comuns

CategoriaOpções
BibliotecasResilience4j (Java), Polly (.NET), tenacity (Python), axios-retry (Node.js)
HTTP ClientsConfiguração nativa em HttpClient (.NET), OkHttp (Java), Got (Node.js)
CloudAWS SDK (retry integrado), Azure SDK (retry policies), Google Cloud Client Libraries
Service MeshIstio, Linkerd, Envoy (retry no nível de infraestrutura)

Relação com outros padrões

O Retry raramente é usado sozinho. Faz parte de uma cadeia de resiliência:

Solicitud


┌──────────┐
│ Timeout  │  Define cuánto esperar por intento
└────┬─────┘

┌──────────┐
│  Retry   │  Reintenta si el intento falla
└────┬─────┘

┌──────────┐
│ Circuit  │  Corta si hay demasiados fallos
│ Breaker  │
└────┬─────┘

Servicio downstream
  • Timeout: Define quanto esperar em cada tentativa individual antes de considerá-la falha
  • Circuit Breaker: Se as retentativas falham consistentemente, o circuit breaker se abre e evita mais tentativas
  • Idempotência: Os serviços downstream devem ser idempotentes para que as retentativas sejam seguras
  • Bulkhead: Isola os recursos para que as retentativas de um serviço não afetem os outros

Próximos passos

O Retry é a primeira linha de defesa contra falhas transitórias. Para definir quanto esperar em cada tentativa, explore o padrão Timeout. Para garantir que as retentativas não causem efeitos duplicados, revise o padrão de Idempotência.