Retry Backoff: Budgets, Deadlines y Config de Librerías
Implementa estrategias de retry resilientes con exponential backoff, jitter e integración de circuit breaker para recuperación de fallas transitorias.
Visión General
El retry con exponential backoff es el patrón fundacional para manejar fallas transitorias en sistemas distribuidos. En lugar de fallar inmediatamente cuando ocurre un network hiccup o sobrecarga temporal, el cliente espera progresivamente más entre intentos. Agregar jitter previene retries sincronizados que crean un thundering herd que abruma al servicio en recuperación.
Cuándo Usar
Usa este recurso cuando:
- Llamas a APIs externas o servicios sobre redes poco confiables
- Conexiones de base de datos ocasionalmente hacen timeout bajo carga
- Necesitas distinguir errores transitorios (reintentables) de fallas permanentes
- Integras con servicios cloud que throttlean o tienen outages regionales
Solución
Exponential Backoff con Jitter (Python)
import random
import time
from functools import wraps
def retry(max_attempts=5, base_delay=1, max_delay=60, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts:
raise
# Exponential backoff con full jitter
delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
jitter = random.uniform(0, delay)
time.sleep(jitter)
return wrapper
return decorator
@retry(max_attempts=5, base_delay=1, exceptions=(ConnectionError,))
def fetch_data(url):
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
Resilience4j Circuit Breaker + Retry (Java)
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
@Service
public class PaymentService {
@Retry(name = "paymentRetry", fallbackMethod = "fallback")
@CircuitBreaker(name = "paymentCircuit")
public PaymentResult charge(PaymentRequest request) {
return paymentClient.charge(request);
}
private PaymentResult fallback(PaymentRequest request, Exception ex) {
return PaymentResult.declined("Service temporarily unavailable");
}
}
// application.yml
resilience4j:
retry:
configs:
default:
maxAttempts: 5
waitDuration: 1s
exponentialBackoffMultiplier: 2
retryExceptions:
- java.net.ConnectException
- java.net.SocketTimeoutException
Polly Retry Policy (C#)
using Polly;
var retryPolicy = Policy
.Handle<HttpRequestException>(ex =>
ex.StatusCode == HttpStatusCode.ServiceUnavailable ||
ex.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(
retryCount: 5,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
+ TimeSpan.FromMilliseconds(new Random().Next(0, 1000)),
onRetry: (exception, timeSpan, retryCount, context) =>
{
logger.LogWarning($"Retry {retryCount} after {timeSpan}s due to {exception.Message}");
});
var result = await retryPolicy.ExecuteAsync(() => httpClient.GetAsync(url));
Explicación
Estrategias de backoff:
| Estrategia | Patrón de Delay | Caso de Uso |
|---|---|---|
| Fijo | 1s, 1s, 1s | Intervalos de retry predecibles |
| Lineal | 1s, 2s, 3s | Incremento moderado |
| Exponencial | 1s, 2s, 4s, 8s | Escape rápido de sobrecarga |
| Decorrelated jitter | Random en [0, 2^n] | Previene thundering herd |
| Equal jitter | (2^n)/2 + random | Spread balanceado |
Cuándo NO reintentar:
- HTTP 400 (client error — retry no lo arregla)
- HTTP 401/403 (issues de auth)
- HTTP 404 (el recurso no existe)
- Errores de lógica de negocio (fondos insuficientes, input inválido)
Variantes
| Librería | Lenguaje | Destacado |
|---|---|---|
| Resilience4j | Java | Retry, CB, rate limiter, bulkhead |
| Polly | C# | Completo; soporte async |
| tenacity | Python | Decorators; soporte jitter |
| cockroachdb/errors | Go | Errores estructurados; retry markers |
| axios-retry | JavaScript | Plugin de Axios; configurable |
Lo que funciona
- Setea un delay máximo: Sin un cap, el backoff puede crecer a horas
- Usa idempotency keys: Reintentar POST requests sin ellos crea duplicados. Consulta idempotencia de mensajes.
- Integración con circuit breaker: Deja de reintentar cuando el servicio está claramente caído. Integra con circuit breaker.
- Loggea cada retry: Retries silenciosos ocultan issues sistémicos
- Respeta headers Retry-After: HTTP 429/503 a menudo incluyen tiempos de espera recomendados
Errores Comunes
- Reintentar todo: Operaciones no idempotentes y client errors deberían fallar fast
- Sin jitter: Retries sincronizados de múltiples clientes recrean la sobrecarga original
- Retries infinitos: Un cliente que reintenta para siempre se convierte en una fuente de DoS
- Bloquear al caller: Retries síncronos en request handlers aumentan tiempos de respuesta
- Reintentar dentro de transacciones: Transacciones de base de datos + retries = escalación de locks
Preguntas frecuentes
¿Cuál es el número correcto de retries?
Usualmente 3-5. Más retries aumentan latencia sin mejorar considerablemente tasas de éxito.
¿Debería reintentar en el cliente o usar una message queue?
Para APIs síncronos: retry en cliente. Para jobs en background: usa una queue con retry built-in.
¿Cómo manejo idempotencia para retries?
Genera un header Idempotency-Key único. El servidor verifica si ya procesó esta key antes. Aprende más en idempotencia de mensajes.
Recursos Relacionados
Arquitectura de Microservicios — Cuándo Usarla y Cuándo No
Guía práctica de microservicios: beneficios, trade-offs, patrones comunes y cuándo elegirlos sobre monolitos. Cubre estrategias de descomposición y complejidad operativa.
GuideEntrevistas de System Design: Framework, Ejemplos y Práctica
Framework práctico para entrevistas de system design: estructura tu respuesta, los conceptos que evalúan los entrevistadores y dos ejemplos resueltos.
GuideTeorema CAP y Trade-offs de Bases de Datos
Teorema CAP explicado para practicantes: trade-offs CP vs AP, PACELC, consistencia tunable por base de datos y cómo evitar split-brain.
RecipePatrones de Comunicación entre Microservicios
Elige entre patrones de comunicación síncronos y asíncronos para arquitecturas de microservicios resilientes.
RecipeMotores de workflows
Orquesta procesos de negocio complejos con workflow engines, state machines y coordinación de tareas de larga duración a través de servicios distribuidos.
RecipeRetry con Backoff Exponencial
Cómo implementar lógica de retry resiliente con backoff exponencial y jitter para fallos transitorios en llamadas de red y APIs.