Retry Pattern

Cómo manejar fallos transitorios en sistemas distribuidos reintentando operaciones con estrategias de backoff para mejorar la resiliencia.

Qué problema resuelve

En sistemas distribuidos, las llamadas entre servicios pueden fallar por razones transitorias: problemas de red momentáneos, servicios reiniciándose, límites de rate limiting temporales o picos de carga. Estos fallos son temporales y la operación probablemente tendrá éxito si se reintenta.

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

El problema es que sin reintentos, un fallo transitorio de milisegundos se convierte en un error visible para el usuario, cuando simplemente esperar un momento y reintentar habría resuelto el problema.

Fallos transitorios comunes

Tipo de falloCausaDuración típica
Timeout de redCongestión momentáneaMilisegundos a segundos
HTTP 503Servicio reiniciándose o sobrecargadoSegundos
HTTP 429Rate limit alcanzadoSegundos a minutos
Connection refusedServicio arrancandoSegundos
DNS resolution failureCaché DNS expiradoMilisegundos

Cómo funciona

El patrón Retry intercepta las llamadas fallidas y las reintenta automáticamente según una estrategia configurada. La clave está en cuándo reintentar, cuántas veces y cuánto esperar entre intentos.

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

Estrategias de espera (backoff)

EstrategiaFórmulaEjemplo (3 intentos)Uso
Fixed delaydelay constante1s, 1s, 1sCasos simples
Exponential backoffdelay × 2^intento1s, 2s, 4sEstándar recomendado
Exponential + jitterrandom(0, delay × 2^intento)0.7s, 1.3s, 3.8sEvita thundering herd
Linear backoffdelay × intento1s, 2s, 3sIncremento predecible

Exponential backoff con jitter

La estrategia más recomendada es exponential backoff con jitter (variación aleatoria). Sin jitter, cuando un servicio se recupera, todos los clientes reintentan al mismo tiempo creando un pico de carga que puede volver a tumbarlo (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 configuración

ParámetroDescripciónValor típico
Max retriesNúmero máximo de reintentos3-5
Initial delayTiempo de espera antes del primer reintento100ms - 1s
Max delayTiempo máximo de espera entre reintentos30s - 60s
Backoff multiplierFactor de multiplicación exponencial2
Retryable errorsCódigos de error que justifican reintento408, 429, 500, 502, 503, 504

Qué reintentar y qué no

✅ 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)

Ventajas

  • Resiliencia ante fallos transitorios: La mayoría de errores de red se resuelven solos
  • Transparente para el usuario: El reintento ocurre sin que el usuario lo note
  • Implementación simple: Es uno de los patrones de resiliencia más fáciles de implementar
  • Configurable: Los parámetros se ajustan según el contexto y la criticidad
  • Complementa otros patrones: Funciona bien con Circuit Breaker y Timeout

Trade-offs / Desventajas

  • Latencia acumulada: Cada reintento añade tiempo de espera total
  • Carga adicional: Los reintentos generan más tráfico hacia el servicio que ya está fallando
  • Operaciones no idempotentes: Reintentar una operación que ya se ejecutó puede causar duplicados
  • Falsos positivos: Reintentar un error permanente desperdicia recursos
  • Thundering herd: Sin jitter, los reintentos sincronizados pueden empeorar la situación
  • Complejidad de configuración: Parámetros incorrectos pueden causar más problemas que soluciones

Cuándo usar

  • Llamadas HTTP a servicios externos o microservicios
  • Operaciones de red que pueden fallar por razones transitorias
  • Consultas a bases de datos que pueden fallar por conexiones agotadas
  • Publicación de mensajes a brokers que pueden estar temporalmente no disponibles
  • Cualquier operación idempotente que falla de forma intermitente

Cuándo evitar

  • Operaciones no idempotentes donde reintentar puede causar efectos secundarios duplicados
  • Cuando el error es claramente permanente (400, 401, 403, 404)
  • Operaciones de larga duración donde el timeout total sería inaceptable
  • Cuando ya tienes un Circuit Breaker abierto (el retry no debe intentar pasar por un circuito abierto)
  • Sistemas donde la latencia adicional de los reintentos es inaceptable

Tecnologías e implementaciones comunes

CategoríaOpciones
LibreríasResilience4j (Java), Polly (.NET), tenacity (Python), axios-retry (Node.js)
HTTP ClientsConfiguración nativa en HttpClient (.NET), OkHttp (Java), Got (Node.js)
CloudAWS SDK (retry integrado), Azure SDK (retry policies), Google Cloud Client Libraries
Service MeshIstio, Linkerd, Envoy (retry a nivel de infraestructura)

Relación con otros patrones

El Retry rara vez se usa solo. Forma parte de una cadena de resiliencia:

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 cuánto esperar en cada intento individual antes de considerarlo fallido
  • Circuit Breaker: Si los reintentos fallan consistentemente, el circuit breaker se abre y evita más intentos
  • Idempotencia: Los servicios downstream deben ser idempotentes para que los reintentos sean seguros
  • Bulkhead: Aísla los recursos para que los reintentos de un servicio no afecten a otros

Próximos pasos

El Retry es la primera línea de defensa contra fallos transitorios. Para definir cuánto esperar en cada intento, explora el patrón Timeout. Para garantizar que los reintentos no causen efectos duplicados, revisa el patrón de Idempotencia.