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 fallo | Causa | Duración típica |
|---|---|---|
| Timeout de red | Congestión momentánea | Milisegundos a segundos |
| HTTP 503 | Servicio reiniciándose o sobrecargado | Segundos |
| HTTP 429 | Rate limit alcanzado | Segundos a minutos |
| Connection refused | Servicio arrancando | Segundos |
| DNS resolution failure | Caché DNS expirado | Milisegundos |
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)
| Estrategia | Fórmula | Ejemplo (3 intentos) | Uso |
|---|---|---|---|
| Fixed delay | delay constante | 1s, 1s, 1s | Casos simples |
| Exponential backoff | delay × 2^intento | 1s, 2s, 4s | Estándar recomendado |
| Exponential + jitter | random(0, delay × 2^intento) | 0.7s, 1.3s, 3.8s | Evita thundering herd |
| Linear backoff | delay × intento | 1s, 2s, 3s | Incremento 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ámetro | Descripción | Valor típico |
|---|---|---|
| Max retries | Número máximo de reintentos | 3-5 |
| Initial delay | Tiempo de espera antes del primer reintento | 100ms - 1s |
| Max delay | Tiempo máximo de espera entre reintentos | 30s - 60s |
| Backoff multiplier | Factor de multiplicación exponencial | 2 |
| Retryable errors | Códigos de error que justifican reintento | 408, 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ía | Opciones |
|---|---|
| Librerías | Resilience4j (Java), Polly (.NET), tenacity (Python), axios-retry (Node.js) |
| HTTP Clients | Configuración nativa en HttpClient (.NET), OkHttp (Java), Got (Node.js) |
| Cloud | AWS SDK (retry integrado), Azure SDK (retry policies), Google Cloud Client Libraries |
| Service Mesh | Istio, 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.