Breaker por fuera, retry por dentro
Retry y circuit breaker en el orden equivocado rompen los dos en silencio. Ambos patrones protegen la misma llamada a una dependencia inestable, pero hacen trabajos opuestos — y cuál envuelves por fuera decide si una caída cuesta milisegundos o se lleva por delante tu presupuesto de latencia. La mayoría de las librerías te dejan anidarlos en cualquier orden, y el anidado equivocado se ve perfecto en cada prueba en verde.
Dos patrones, una llamada
Un retry asume que las fallas son transitorias: el timeout parpadeó, el pod estaba reiniciándose, la conexión se reseteó. Vuelve a intentar — idealmente unas pocas veces, con backoff y jitter — esperando que el siguiente intento tenga éxito.
Un circuit breaker asume lo contrario. Tras un umbral de fallas recientes se abre (trips open) y, durante una ventana de enfriamiento, rechaza las llamadas al instante en vez de dejarlas golpear una dependencia que ya está en el suelo. Es una máquina de estados — Closed, Open, Half-Open — que evita que sigas martillando un servicio mientras se recupera, y evita que una dependencia lenta amarre todos los hilos de tu app.
Tiran en direcciones distintas a propósito. El retry dice "intenta más fuerte". El breaker dice "deja de intentar". Justo por eso el orden importa.
La regla: breaker(retry(call))
Envuelve el breaker por fuera y el retry por dentro:
breaker(
retry(
call ) )
Ahora el breaker ve cada llamada como una sola unidad de trabajo. El retry gasta sus intentos adentro; el breaker registra un resultado — éxito o falla final. Una vez que el breaker está Open, toda la secuencia de retry se salta y la llamada falla en microsegundos.
Invierte el orden — retry(breaker(call)) — y cada intento del retry golpea el breaker por separado. Una sola petición lógica ahora registra tres o cinco fallas, así que el breaker se abre demasiado pronto por conteos inflados. Peor aún, cuando está Open, tu bucle de retry sigue llamándolo, recorriendo su backoff contra un breaker diseñado para que te detengas.
Lo que cuesta el orden equivocado
breaker(retry(call)) — correcto | retry(breaker(call)) — invertido | |
|---|---|---|
| Fallas que cuenta el breaker por petición | 1 | hasta N (una por intento) |
| Cuando el breaker está Open | se salta todo el retry, falla rápido | el bucle de retry sigue girando sobre el backoff |
| Umbral de apertura | ajustado a resultados reales | se abre pronto por conteos inflados |
| Latencia durante una caída | microsegundos | todo el presupuesto de retry desperdiciado |
Resilience4j es la trampa clásica: su orden de aspectos por defecto es Retry(CircuitBreaker(...)) — retry por fuera — que es la disposición invertida, y es una fuente documentada de conteos de falla inflados. Si lo usas, pon circuitBreakerAspectOrder por encima de retryAspectOrder para que el breaker envuelva al retry.
Movidas de experto
- Haz que el retry conozca al breaker. La guía de Microsoft es explícita: la lógica de retry debe ser sensible a las excepciones que lanza el breaker y detenerse cuando el breaker indica que la falla no es transitoria. No reintentes un error
CircuitBreakerOpen/ call-not-permitted — es el breaker diciéndote que te frenes, no un parpadeo transitorio. - Agrega un timeout por intento dentro del retry, y un timeout total por fuera de todo. El timeout externo limita todo el presupuesto de retry para que un llamador nunca espere más que su SLA; el interno le da a cada intento un reloj nuevo.
- Un breaker por dependencia, no por método. Si un solo breaker cubre varios backends independientes (shards, regiones), un shard malo abre el circuito para todos. Delimita los breakers al recurso que realmente falla en conjunto.
- No reintentes estados no transitorios. Un
4xx(bad request, auth) fallará igual cada vez — reintentarlo solo le da basura al breaker. Reserva los retries para timeouts,5xxy resets de conexión.
Recursos
- Circuit Breaker pattern — Azure Architecture Center
- Retry pattern — Azure Architecture Center
- Resilience4j — getting started y orden de decoradores
- Resilience4j issue #2383 — el orden por defecto infla los conteos de falla
- Circuit breaker strategy — Polly docs
¿Construyes sistemas de IA y backend resilientes? Yeda AI diseña, audita y despliega servicios en producción que fallan con gracia. Este es un tip de la serie Yeda AI Tips — patrones cortos y prácticos para ingenieros que construyen con IA.