Reintentos y Backoff
Los fallos transitorios de red e infraestructura a menudo tienen éxito al reintentar. Utiliza intentos limitados, backoff exponencial con jitter, y reintenta solo operaciones idempotentes; bibliotecas como tenacity codifican el patrón de forma limpia.
Receta
import random
import time
def retry(callable_fn, attempts=3, base=0.5):
for attempt in range(1, attempts + 1):
try:
return callable_fn()
except TransientError:
if attempt == attempts:
raise
delay = base * (2 ** (attempt - 1)) + random.uniform(0, 0.1)
time.sleep(delay)Cuándo usar esto:
- HTTP 429/503, reinicios de conexión, fallos DNS momentáneos
- Bloqueos de base de datos o fallos de serialización (con cuidado)
- Tiempos de espera en la publicación de colas de mensajes
- Límites de tasa de API en la nube con semántica Retry-After
- NO para errores de validación o errores de cliente 4xx
Ejemplo de Trabajo
Reintento manual con jitter, luego estructura equivalente al estilo tenacity (demostración solo con stdlib).
import random
import time
from dataclasses import dataclass
class TransientError(Exception):
pass
@dataclass
class FakeClient:
calls: int = 0
fail_until: int = 2
def get(self) -> str:
self.calls += 1
if self.calls <= self.fail_until:
raise TransientError(f"intento {self.calls}")
return "ok"
def with_retry(client: FakeClient, attempts: int = 5) -> str:
for attempt in range(1, attempts + 1):
try:
return client.get()
except TransientError as exc:
if attempt == attempts:
raise RuntimeError("reintentos agotados") from exc
delay = min(2.0, 0.2 * (2 ** (attempt - 1))) + random.uniform(0, 0.05)
time.sleep(delay)
raise RuntimeError("no alcanzable")
client = FakeClient()
print(with_retry(client), "llamadas:", client.calls)Lo que esto demuestra:
- Limitar los intentos y lanzar un error encadenado cuando se agotan
- Backoff exponencial limitado para evitar esperas de varios minutos en código síncrono
- Jitter reduce el efecto de "manada de truenos" en la recuperación
- Contar llamadas en pruebas para verificar el comportamiento de reintento
Inmersión Profunda
Cómo Funciona
- Clasificar excepciones en retriables vs permanentes
- Backoff:
delay = min(cap, base * 2**attempt) + jitter - Idempotencia: los reintentos son seguros solo si los efectos secundarios están ausentes o deduplicados
- tenacity proporciona decoradores:
stop_after_attempt,wait_exponential,retry_if_exception_type - Registrar cada reintento con el número de intento y la duración del sueño
Tabla de Política de Reintento
| Señal | ¿Reintentar? |
|---|---|
| Tiempo de espera, 503 | Sí, con backoff |
| Validación 400, 422 | No |
| Conflicto 409 | Solo con fusión idempotente |
| Éxito | Detener |
Errores Comunes
- Reintentar POST no idempotente - duplica cargos/pedidos. Solución: claves de idempotencia o saltar el reintento.
- Bucles de reintento infinitos - sin condición de
stopquema CPU y enmascara interrupciones. Solución: intentos máximos + circuit breaker. - Sin jitter - los clientes sincronizados reintentan juntos. Solución: jitter uniforme aleatorio o completo.
- Reintentar demasiado rápido - empeora los límites de tasa. Solución: honrar la cabecera
Retry-Aftercuando esté presente. - Ignorar la excepción final - devolver None después de fallos. Solución: lanzar con contexto al agotar los intentos.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Circuit breaker | Interrupción sostenida | Fallo único |
| Cola + cola de mensajes fallidos | Trabajadores asíncronos | Ruta de solicitud síncrona |
| Reintento incorporado en la biblioteca cliente | boto/urllib3 configurado | Necesita registro/métricas personalizadas |
Preguntas Frecuentes
¿Cuándo debo usar tenacity?
Cuando los decoradores y las políticas de stop/wait componibles superan a los bucles hechos a mano; la mayoría de los servicios de producción.
¿Cuántos reintentos son suficientes?
3-5 para HTTP con backoff exponencial es común; ajustar con SLOs y presupuestos de latencia p99.
¿Deben los reintentos registrar advertencias?
Sí - incluir intento, espera, tipo de excepción e ID de correlación para la correlación de incidentes.
¿Son seguros los reintentos de base de datos?
Solo para errores transitorios conocidos (bloqueo, pérdida de conexión) en sentencias idempotentes.
¿Cómo difieren los reintentos asíncronos?
Usar asyncio.sleep en lugar de time.sleep; tenacity soporta llamables asíncronos.
¿Qué pasa con los reintentos en middleware?
Centralizar la política para clientes HTTP salientes; no reintentar doble en múltiples capas.
¿Puedo reintentar en valores de retorno?
retry_if_result de tenacity funciona cuando los "fallos suaves" devuelven códigos de error en lugar de lanzar excepciones.
¿Cómo pruebo el backoff?
Inyectar mock de reloj/sleep; afirmar el recuento de intentos sin demoras reales (tenacity wait_fixed(0) en pruebas).
¿FastAPI reintenta automáticamente?
No - la aplicación o el cliente HTTP (transporte httpx) poseen la política de reintento.
¿Qué es el jitter completo?
sleep = random.uniform(0, cap) - variante recomendada por AWS para distribuir la carga en la recuperación.
Relacionado
- Lanzamiento y Encadenamiento - cadena de fallo final
- El Módulo de Logging - advertencias de reintento
- HTTP Asíncrono con httpx/aiohttp - tiempos de espera del cliente
- Mejores Prácticas de Errores y Logging - política operativa
Versiones de la Pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.