El backoff simple es una estampida
Tu backoff exponencial es una estampida apuntada a tu propia API. Cuando una dependencia falla, cada cliente que falló en el mismo instante espera el mismo retardo fijo, y luego reintenta en el mismo instante. El backoff simple no reparte la carga. La sincroniza, y esa ola sincronizada vuelve a hacerle DDoS al mismo servicio que intentaba recuperarse.
Por qué el backoff simple lo empeora
El backoff exponencial debía ser la solución: ante cada fallo, esperar base × 2^attempt, con un tope. La espera funciona. El problema es que una flota de clientes que fallaron juntos comparte un reloj. Todos duermen 1s, luego 2s, luego 4s, al mismo paso. Cada ventana de reintento se vuelve un pulso coordinado. El libro de SRE de Google lo dice claro: si los reintentos no se distribuyen aleatoriamente dentro de la ventana, una pequeña perturbación como un corte de red puede agendar oleadas de reintentos al mismo tiempo, y estas se amplifican solas.
La solución: full jitter
No duermas el retardo del backoff. Duerme una cantidad aleatoria entre cero y el retardo del backoff. Eso es full jitter, del estudio "Exponential Backoff And Jitter" de AWS:
# Backoff exponencial simple (la estampida)
sleep = min(cap, base * 2 ** attempt)
# Full jitter (el goteo)
sleep = random_between(0, min(cap, base * 2 ** attempt))
El tope sigue creciendo exponencialmente, así que un servicio en apuros igual gana exponencialmente más aire. Pero como cada cliente saca su propia espera aleatoria, los reintentos se untan sobre toda la ventana en vez de apilarse en un mismo tick.
Lo que dicen los números
AWS simuló 100 clientes en contención contra un recurso compartido y midió dos cosas: total de llamadas (carga del servidor / contención) y tiempo para completar todo el trabajo.
| Estrategia | Fórmula de espera | Vs. sin jitter |
|---|---|---|
| Sin jitter | min(cap, base·2^n) | base: alta contención |
| Full jitter | rand(0, min(cap, base·2^n)) | ~½ de las llamadas, completa mucho antes |
| Equal jitter | t/2 + rand(0, t/2), t = min(cap, base·2^n) | llamadas similares, pero más lento que full |
| Decorrelated | min(cap, rand(base, last·3)) | más llamadas que full, algo más rápido |
Full jitter tuvo el menor trabajo del cliente y un tiempo de finalización competitivo. La conclusión de AWS: el backoff con jitter es enorme y debe ser un enfoque estándar para clientes remotos.
Notas para usuarios avanzados
- Acota el tope, no el jitter. El
caplimita el backoff máximo para que un cliente no duerma minutos. El jitter muestrea por debajo del tope; mantén el rango completo[0, cap]; encogerlo hacia el valor determinista vuelve a sincronizar la estampida. - Decorrelated jitter para clientes de larga vida.
sleep = min(cap, random(base, last_sleep × 3))sube el retardo según la espera anterior en vez del número de intento. Un poco más agresivo, igual de desincronizado; útil cuando los clientes no reinician bien el contador de intentos. - Agrega un presupuesto de reintentos y un circuit breaker. El jitter dispersa los reintentos; no reduce su total. Limita los reintentos por solicitud y deja de reintentar cuando un servicio claramente está caído, para no darle jitter a una manguera.
- Todo cliente RPC, no solo el que falla. La guía de SRE es que cada cliente que hace una llamada implemente backoff exponencial con jitter, porque la amplificación de errores es una propiedad de toda la flota.
Recursos
- Exponential Backoff And Jitter — AWS Architecture Blog — la simulación, las fórmulas y la comparación full/equal/decorrelated.
- Jitter: Making Things Better With Randomness — Marc Brooker — la mirada más amplia del autor sobre jitter en sistemas distribuidos.
- Addressing Cascading Failures — Google SRE Book, Cap. 22 — por qué el backoff exponencial aleatorizado amortigua las oleadas de reintentos.
<div class="cta"> <p><strong>¿Construyes sistemas de IA resilientes?</strong> Yeda AI diseña, audita y despliega infraestructura de LLM y backend en producción que aguanta la carga.</p> <p><a href="/contact">Hablemos</a> · <a href="/blog">Lee el blog</a></p> </div>