Cuando un servicio upstream falla durante unos segundos, todos los nodos de un sistema distribuido detectan el error al mismo tiempo y, si aplican un retroceso exponencial sin variación, reintentan de forma sincronizada. Ese pico de tráfico coordinado puede hundir al servicio que apenas se estaba recuperando. Este fenómeno se conoce como thundering herd y es uno de los problemas más comunes en sistemas que dependen de comunicaciones externas. La solución más extendida es incorporar jitter, una componente aleatoria que dispersa los reintentos en el tiempo. En Q2BSTUDIO, donde trabajamos habitualmente con aplicaciones a medida de alta disponibilidad, este patrón forma parte del núcleo de cualquier integración robusta.

El retroceso exponencial clásico calcula el tiempo de espera como base multiplicado por dos elevado al número de intento. Sin jitter, todos los clientes obtienen el mismo valor y reintentan exactamente al mismo instante. El resultado es una carga en forma de diente de sierra que sobrecarga al upstream justo cuando intenta recuperarse. La recomendación estándar, documentada por los equipos de ingeniería de infraestructura cloud, es añadir una componente aleatoria dentro de la ventana de retroceso. Existen tres variantes principales: full jitter, que elige un valor uniforme entre cero y el techo exponencial; equal jitter, que fija la mitad del valor como suelo y aleatoriza la otra mitad; y decorrelated jitter, que usa el tiempo de espera anterior para calcular el siguiente sin depender del número de intento. La primera ofrece la menor carga total sobre el servidor, mientras que la tercera minimiza la latencia total de la operación. Para la mayoría de los servicios, full jitter es la elección más sensata.

Implementar un bucle de reintentos en TypeScript que sea digno de producción requiere mucho más que un setTimeout dentro de un bucle. Necesitamos una señal de aborto que cancele la espera cuando el usuario o un plazo global lo indiquen; un mecanismo de sueño que escuche esa señal y rechace la promesa si se aborta; un límite tanto en número de intentos como en tiempo total transcurrido; y una función shouldRetry que distinga entre errores transitorios y errores permanentes. Además, para operaciones de escritura como POST, es imprescindible usar claves de idempotencia: generar un UUID antes del primer intento y enviarlo en cada reintento para que el servidor pueda deduplicar. Sin esta precaución, un reintento puede provocar cobros duplicados o estados inconsistentes. En los proyectos de servicios cloud aws y azure que desarrollamos, estas claves se convierten en un requisito no negociable.

Un esqueleto mínimo en TypeScript ocupa aproximadamente cien líneas. La función withRetry recibe una operación asíncrona y un objeto de opciones con maxAttempts, baseMs, capMs, deadlineMs, signal, shouldRetry y un callback onRetry para monitoreo. Internamente, construye una señal compuesta combinando la señal del usuario con un timeout de plazo global usando AbortSignal.any, disponible en los runtimes modernos. La operación recibe un objeto Attempt que expone el número de intento y la señal, permitiendo al llamado estampar cabeceras de traza y reaccionar a cancelaciones. En cada fallo, se calcula el retroceso con full jitter, se verifica si el error incluye una cabecera Retry-After y se espera el máximo entre el jitter y ese valor. Si el error se considera no reintentable, se lanza de inmediato. Este mismo patrón se puede extender con circuit breakers, bulkheads o hedging, y se integra de forma natural con sistemas de inteligencia artificial que necesitan robustez ante fallos intermitentes de APIs externas.

La clave de una política de reintentos bien diseñada es que no empeore el problema que intenta resolver. Por eso es crucial no reintentar errores de validación, autenticación o recursos no encontrados. También conviene establecer un límite de tiempo total (deadlineMs) que sea inferior al timeout del contexto superior, dejando margen para la respuesta final. En Q2BSTUDIO aplicamos estos principios tanto en soluciones de ciberseguridad como en plataformas de inteligencia de negocio con Power BI, donde la fiabilidad de las fuentes de datos es crítica. La experiencia demuestra que un reintento sin jitter, sin señales de aborto y sin control de idempotencia es la receta perfecta para un incidente en producción. Con cien líneas bien escritas, cualquier equipo puede evitar ese destino y construir sistemas que realmente resistan los vaivenes de los servicios externos.