Circuit breakers mal configurados: por qué fallan cuando más los necesitas
ArquitecturaSistemas CríticosCircuit BreakerResiliencia

Circuit breakers mal configurados: por qué fallan cuando más los necesitas

Un circuit breaker con el umbral equivocado no te protege — falla en silencio o dispara falsos positivos. Cómo configurarlo con resilience4j sin repetir los errores más comunes.

Daniel Jordan
5 min read
Compartir:

El síntoma: el breaker que estaba ahí y no protegió nada

Un servicio de pagos empieza a responder con latencia alta — no caído del todo, solo lento. Las peticiones se acumulan, los hilos se agotan esperando respuesta, y el fallo se propaga hacia arriba en cascada hasta tumbar servicios que ni siquiera dependen directamente del que falló.

Había un circuit breaker configurado. Nunca se abrió. El umbral estaba puesto para detectar fallos explícitos (excepciones, timeouts duros) — no llamadas lentas que técnicamente "funcionan" pero tardan 8 segundos en devolver una respuesta.

Qué hace un circuit breaker, exactamente

La idea, descrita originalmente por Michael Nygard en Release It! y popularizada por Martin Fowler: envuelves una llamada a un servicio remoto en un objeto que monitoriza fallos. Cuando los fallos superan un umbral, el circuito se "abre" y las siguientes llamadas fallan inmediatamente, sin intentar la llamada real — dando tiempo al servicio caído para recuperarse en vez de seguir golpeándolo.

Tres estados operativos, no dos — y dos estados de gestión manual:

CLOSED (normal) --- exceso de errores/latencia --> OPEN (bloqueo)
    ^                                                  |
    |                                                  | tras waitDurationInOpenState
    +-------------- éxito en prueba ------ HALF_OPEN (evaluación) <---+
                                                |
                                                +-- fallo en prueba --> vuelve a OPEN
  • CLOSED: operación normal, las llamadas pasan y se monitorizan.
  • OPEN: el umbral de fallos se superó, las llamadas fallan al instante sin tocar el servicio real — en resilience4j, lanzando una CallNotPermittedException local, sin generar tráfico de red.
  • HALF_OPEN: tras un tiempo de espera, deja pasar un número limitado de llamadas de prueba para comprobar si el servicio se recuperó.
  • DISABLED / FORCED_OPEN: estados de gestión manual, para forzar el paso de tráfico o bloquearlo por completo durante mantenimiento — no forman parte del ciclo automático.

Los tres errores de configuración más comunes

1. Medir solo fallos explícitos, no llamadas lentas

Una llamada que tarda 8 segundos en devolver un 200 OK no cuenta como fallo si solo miras excepciones — pero satura hilos y conexiones exactamente igual que un error real. resilience4j separa esto explícitamente con slowCallRateThreshold y slowCallDurationThreshold, aparte del failureRateThreshold de errores duros.

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .slowCallRateThreshold(50)
    .slowCallDurationThreshold(Duration.ofSeconds(2))
    .slidingWindowType(SlidingWindowType.COUNT_BASED)
    .slidingWindowSize(100)
    .minimumNumberOfCalls(20)
    .waitDurationInOpenState(Duration.ofSeconds(10))
    .permittedNumberOfCallsInHalfOpenState(3)
    .automaticTransitionFromOpenToHalfOpenEnabled(true)
    .build();

Sin slowCallRateThreshold, un servicio que responde lento pero "sin error" nunca abre el circuito — exactamente el caso del síntoma inicial.

2. Umbral demasiado sensible — falsos positivos que degradan disponibilidad innecesariamente

Un minimumNumberOfCalls bajo combinado con una ventana pequeña hace que 2-3 fallos aislados (un timeout puntual de red, nada estructural) abran el circuito. El resultado: el sistema deja de llamar a un servicio que en realidad estaba sano, auto-infligiéndose la interrupción que el patrón debía evitar.

3. Recuperación demasiado agresiva en HALF_OPEN

Si permittedNumberOfCallsInHalfOpenState es alto y el servicio aún no se ha recuperado del todo, el breaker vuelve a abrirse casi al instante — oscilando entre OPEN y HALF_OPEN en vez de darle al servicio tiempo real de recuperación. Un número bajo (2-5) de llamadas de prueba es más seguro que uno alto.

Ventana deslizante: por conteo vs. por tiempo

El motor de métricas del circuit breaker se basa en una ventana deslizante, y el tipo correcto depende del patrón de tráfico del servicio:

Criterio Por conteo (COUNT_BASED) Por tiempo (TIME_BASED)
Mecanismo Array circular de las últimas N llamadas Agrupa métricas en bloques de T segundos
Caso ideal Tráfico alto y constante Tráfico variable, con picos (bursts)
Riesgo En servicios de tráfico bajo, un fallo de hace horas puede seguir contando y mantener el circuito abierto o cerrado de forma errónea

Para un servicio con tráfico constante (como un endpoint de pagos con volumen estable), COUNT_BASED es más predecible. Para servicios con picos irregulares, TIME_BASED evita que un pico puntual antiguo siga pesando en la evaluación actual.

Observabilidad: qué métricas exponer

Un circuit breaker no debe operar como caja negra — su cambio de estado tiene que verse en el sistema de monitoreo antes de que se convierta en incidente. Con la integración de Micrometer y Prometheus, las métricas clave son:

  • Estado del circuito: resilience4j_circuitbreaker_state{name="paymentService"} (0=CLOSED, 1=OPEN, 2=HALF_OPEN) — alerta de severidad alta si conmuta a OPEN.
  • Porcentaje de fallos: resilience4j_circuitbreaker_failure_rate{name="paymentService"}
  • Peticiones bloqueadas: resilience4j_circuitbreaker_calls_seconds_count{kind="not_permitted"} — cuántas peticiones se rechazaron sin llegar a la red.

Criterio de decisión

Antes de aceptar la configuración por defecto de cualquier librería de circuit breaker: ¿este servicio puede fallar lento (timeout parcial) o solo falla rápido (excepción)? Si puede fallar lento, slowCallRateThreshold no es opcional — sin él, el breaker es una falsa sensación de protección. Como punto de partida razonable: minimumNumberOfCalls de al menos 20, y entre 2 y 5 llamadas de prueba en permittedNumberOfCallsInHalfOpenState.

Referencias

  • Martin Fowler — CircuitBreaker (artículo original que popularizó el patrón, basado en el libro Release It! de Michael Nygard).
  • resilience4j — CircuitBreaker documentation (documentación oficial, incluye la máquina de estados completa y todas las opciones de configuración).
  • Netflix — Hystrix (la librería de circuit breaker original de Netflix, hoy en modo mantenimiento — el propio repositorio recomienda resilience4j para proyectos nuevos, lo que explica por qué es el estándar actual).

Lecturas relacionadas

Tags:#circuit-breaker#resiliencia#microservicios#resilience4j#sistemas-distribuidos