Nunca Escribas tu Propia Lógica de Reintentos
Tu bucle de reintentos está reintentando errores que nunca pueden funcionar. Un 500 probablemente se resuelva en el próximo intento; un 400, 401 o 422 va a fallar exactamente igual todas las veces — así que un for attempt in range(5) ingenuo solo multiplica por cinco la misma petición condenada, satura un servicio que ya está sufriendo y quema tu presupuesto de latencia sin ningún beneficio.
Por qué fallan los reintentos hechos a mano
El bucle de tres líneas que todos escriben acierta en lo fácil y falla en lo difícil:
- Sin backoff. Reintentar al instante apila peticiones sobre un servicio que ya está fallando — la clásica tormenta de reintentos que convierte un bache en una caída total.
- Sin jitter. Incluso con backoff, cada cliente que falló en el mismo momento reintenta en el mismo momento. Un retardo aleatorio ("jitter") los dispersa para que no vuelvan a chocar.
- Sin clasificación de errores. El bucle reintenta lo que sea que atrapó, incluidos los errores
4xxdel cliente — entrada inválida, falta de autenticación, fallos de validación — que son culpa tuya y nunca van a pasar en un reintento.
La matemática del backoff, el jitter y el estado del circuit breaker son lo bastante sutiles como para que una librería probada lo haga mejor que el código que escribes contra reloj.
La regla: reintenta lo transitorio, salta los errores del cliente
| Estado | Significado | ¿Reintentar? |
|---|---|---|
408, 429, 500, 502, 503, 504 | Timeout, límite de tasa, error del servidor | Sí — transitorio |
| Conexión reiniciada / DNS / timeout de socket | Bache de red | Sí — transitorio |
400, 401, 403, 404, 422 | Petición inválida, auth, no encontrado, validación | No — permanente |
La única excepción que conviene saber: 429 Too Many Requests es un 4xx pero sí es reintentable — respeta su cabecera Retry-After en vez de reintentar a ciegas.
Usa una librería
Python — tenacity:
from tenacity import (retry, stop_after_attempt,
wait_random_exponential, retry_if_exception_type)
@retry(
retry=retry_if_exception_type(TransientError), # skip 4xx
wait=wait_random_exponential(multiplier=1, max=60), # backoff + jitter
stop=stop_after_attempt(5),
)
def call_api():
...
wait_random_exponential te da backoff exponencial y jitter en una sola estrategia; retry_if_exception_type es donde filtras solo los errores transitorios.
JavaScript — p-retry: lanza AbortError para detenerte de inmediato ante un error del cliente, sin consumir los intentos restantes.
import pRetry, {AbortError} from 'p-retry';
await pRetry(async () => {
const res = await fetch(url);
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
throw new AbortError(res.statusText); // permanente — detente ya
}
if (!res.ok) throw new Error(res.statusText); // transitorio — reintenta
return res.json();
}, {retries: 5, factor: 2});
Sección avanzada: hacer que los reintentos sean realmente seguros
Un reintento reenvía una petición que el servidor quizá ya procesó. Eso solo es seguro si la operación es idempotente — ejecutarla dos veces tiene el mismo efecto que ejecutarla una vez.
GET,PUTyDELETEson idempotentes por diseño.POSTnormalmente no lo es.- Para escrituras no idempotentes, envía una clave de idempotencia (una cabecera/campo único por operación lógica) para que el servidor descarte una petición reenviada en vez de cobrar la tarjeta dos veces.
- Acota todo. Limita los intentos (
stop_after_attempt) y la espera total (max), para que una petición muerta deje de fallar en bucle en vez de reintentar para siempre. - Agrega un circuit breaker delante del reintento para llamadas de alto volumen: una vez que una dependencia está claramente caída, falla rápido durante una ventana de enfriamiento en vez de dejar que cada llamador reintente contra el vacío.
Reintenta lo transitorio, salta el 4xx, usa claves en tus escrituras y acota el bucle — luego deja que una librería cargue con la matemática.
Recursos
- tenacity — librería de reintentos para Python —
wait_random_exponential,stop_after_attempt,retry_if_exception_type. - p-retry — reintenta una función que devuelve una promesa —
retries,factoryAbortError. - MDN — Códigos de estado de respuesta HTTP — la división
4xxvs5xx. - MDN — Cabecera Retry-After — para
429y503. - Métodos idempotentes (glosario MDN) — qué métodos HTTP son seguros de reenviar.
<div class="cta">
¿Construyendo una función con IA? Yeda AI diseña, audita y lanza sistemas LLM en producción.
Habla con nosotros · Lee el blog
</div>