El patrón existe para impedir que una aplicación siga enviando trabajo hacia una dependencia que ya ha demostrado estar degradada. Cuando detecta una proporción suficiente de errores o llamadas lentas, abre el circuito y rechaza temporalmente nuevas ejecuciones. Después permite un número limitado de llamadas de prueba para decidir si la dependencia puede volver a recibir tráfico.
La idea parece simple. La implementación no lo es.
Para que el patrón sea útil hay que responder, como mínimo, estas preguntas:
- ¿Qué resultados cuentan como fallo?
- ¿Cuántas llamadas forman una muestra representativa?
- ¿Qué significa que una llamada sea lenta?
- ¿Cuánto tiempo debe permanecer abierto el circuito?
- ¿Cuántas solicitudes se usarán para probar la recuperación?
- ¿Qué verá el usuario cuando una llamada sea rechazada?
- ¿Cómo sabrá el equipo que el circuito está protegiendo el sistema y no ocultando otro problema?
El Circuit Breaker no es un interruptor que se activa y queda resuelto. Es una política operativa codificada.
Qué problema resuelve un Circuit Breaker
Supongamos que una aplicación llama a un servicio externo que normalmente responde en 200 ms. La dependencia empieza a degradarse y cada solicitud demora ocho segundos antes de terminar en timeout.
Sin una política de protección, el cliente continúa enviando llamadas. Mientras espera:
- mantiene conexiones ocupadas;
- retiene threads o tareas asíncronas;
- incrementa la profundidad de las colas;
- consume memoria;
- eleva la latencia de otras operaciones;
- puede activar reintentos que multiplican la carga;
- termina propagando la degradación hacia componentes que estaban sanos.
El error original pertenece a una dependencia. El incidente resultante ya pertenece a todo el sistema.
El Circuit Breaker introduce una decisión: cuando la evidencia indica que seguir llamando tiene una probabilidad baja de éxito y un costo alto, deja de ejecutar temporalmente la operación.
En vez de esperar ocho segundos para obtener otro timeout, la aplicación rechaza la llamada de inmediato. Esa respuesta rápida no recupera la dependencia, pero evita seguir gastando capacidad en una operación con pocas posibilidades de terminar bien.
Fallar rápido no significa ocultar la falla
Abrir el circuito no convierte la dependencia en saludable. Tampoco elimina el impacto funcional.
El patrón protege recursos y limita propagación. La aplicación aún debe decidir qué hacer con la operación rechazada:
- devolver un error explícito;
- servir datos previamente almacenados y marcarlos como potencialmente desactualizados;
- degradar una función no crítica;
- encolar trabajo para procesamiento posterior, si la semántica lo permite;
- bloquear una acción que no puede ejecutarse con seguridad.
La peor salida es confirmar una operación que nunca ocurrió.
Los tres estados del Circuit Breaker
Un Circuit Breaker se comporta como una máquina de estados. Las bibliotecas pueden añadir estados administrativos o de observación, pero el comportamiento normal gira alrededor de tres.
Closed → Open cuando la tasa de fallos o de
llamadas lentas supera el umbral con muestra mínima suficiente); el tiempo de espera
(Open → Half-Open al terminar el wait duration); las llamadas de prueba
que decide la recuperación (Half-Open → Closed) o la recaída
(Half-Open → Open).
Closed
En estado Closed, las llamadas pasan hacia la dependencia.
El Circuit Breaker registra el resultado y la duración de cada ejecución dentro de una ventana. Mientras la proporción de fallos y llamadas lentas se mantenga por debajo de los umbrales configurados, el circuito continúa cerrado.
Closed no significa que la dependencia nunca falle. Significa que la evidencia disponible todavía no justifica interrumpir el tráfico.
Open
Cuando la tasa de fallos o de llamadas lentas supera el umbral y existe una muestra mínima suficiente, el circuito pasa a Open.
En este estado, las nuevas llamadas no llegan a la dependencia. Se rechazan localmente.
Esto permite:
- liberar recursos del cliente;
- reducir presión sobre el servicio degradado;
- evitar esperas que ya no aportan valor;
- dar tiempo a que una recuperación automática o una intervención operativa tenga efecto.
El circuito permanece abierto durante un intervalo definido. Ese tiempo no debe interpretarse como “el tiempo que tarda la dependencia en recuperarse”. Es solo el periodo tras el cual vale la pena volver a comprobarla.
Half-Open
Después del intervalo de espera, el Circuit Breaker entra en Half-Open y permite un número limitado de llamadas de prueba.
No se debe devolver todo el tráfico inmediatamente. La recuperación puede ser parcial, inestable o temporal.
Si las llamadas de prueba cumplen los criterios de salud, el circuito vuelve a Closed. Si fallan o siguen siendo demasiado lentas, regresa a Open.
Half-Open es una etapa de verificación, no una declaración de recuperación.
Cómo decide abrir el circuito
La configuración debe representar el comportamiento esperado de una dependencia concreta. Copiar valores de otro servicio es una forma rápida de obtener un patrón que reacciona tarde, demasiado pronto o por las razones equivocadas.
Ventana por cantidad de llamadas
Una ventana basada en cantidad evalúa las últimas N ejecuciones.
Ejemplo:
- tamaño de ventana: 100 llamadas;
- muestra mínima: 50 llamadas;
- umbral de fallos: 50 %.
El Circuit Breaker puede abrir cuando, dentro de la muestra evaluada, al menos la mitad de las llamadas falla.
Este modelo es fácil de razonar cuando el tráfico es relativamente estable. En sistemas con ráfagas, esas 100 llamadas pueden representar varios minutos o solo una fracción de segundo.
Ventana por tiempo
Una ventana basada en tiempo evalúa las llamadas ocurridas durante los últimos N segundos.
Ejemplo:
- ventana: 30 segundos;
- muestra mínima: 20 llamadas;
- umbral de fallos: 50 %.
Este enfoque alinea la evaluación con un periodo operativo, pero sigue necesitando un volumen mínimo. Sin ese requisito, dos fallos dentro de una ventana con muy poco tráfico podrían abrir el circuito usando una muestra estadísticamente débil.
Failure rate threshold
El failure rate threshold define qué proporción de llamadas registradas como fallidas provoca la apertura.
El punto crítico no es elegir 40 %, 50 % o 60 %. Es definir correctamente qué se registra como fallo.
Normalmente pueden contar:
- errores de red;
- timeouts;
- respuestas 5xx específicas;
- resultados funcionales que representan indisponibilidad técnica.
Normalmente no deberían contar de la misma forma:
- errores de validación;
- solicitudes mal formadas;
- credenciales inválidas;
- reglas de negocio rechazadas correctamente;
- cancelaciones provocadas porque el consumidor abandonó la operación.
Si cada excepción incrementa la tasa de error, el circuito puede abrir por un problema del cliente o por una condición funcional que la dependencia procesó correctamente.
Slow call rate threshold
Una dependencia no necesita devolver errores para ser peligrosa. Puede responder correctamente, pero hacerlo tan tarde que retenga recursos, venza deadlines y deteriore toda la cadena.
El slow call rate threshold permite abrir el circuito cuando un porcentaje de llamadas supera una duración definida.
Para usarlo hay que determinar primero qué significa “lento” para esa operación. Una consulta que normalmente tarda 50 ms no debería compartir el mismo límite que un proceso diseñado para completar en cinco segundos.
El umbral debe relacionarse con:
- el SLO de la operación;
- el presupuesto de latencia end-to-end;
- el timeout por intento;
- la capacidad del pool o la cola;
- el costo de mantener una ejecución en curso.
Puedes profundizar en esta relación en latencia en sistemas distribuidos.
Minimum number of calls
La muestra mínima evita tomar una decisión drástica con poca evidencia.
Imagina un servicio que recibe cuatro llamadas por minuto. Si las dos primeras fallan y el umbral es 50 %, abrir inmediatamente puede impedir las siguientes solicitudes durante un periodo prolongado, aunque la dependencia ya se haya recuperado.
En el extremo contrario, una muestra mínima demasiado grande puede impedir que el circuito abra durante una degradación real.
¿Cuántas observaciones necesito para distinguir un fallo aislado de una condición persistente sin reaccionar demasiado tarde?
Wait duration in Open State
El tiempo en Open debe ser suficiente para evitar pruebas constantes, pero no tan largo que prolongue una indisponibilidad después de que la dependencia se recuperó.
Puede derivarse de:
- tiempos habituales de recuperación;
- comportamiento de autoscaling;
- reinicios o failovers;
- ventanas de mantenimiento;
- límites de la dependencia;
- costo de una prueba fallida.
Un intervalo fijo es un punto de partida, no una verdad universal. En sistemas avanzados puede ajustarse según fallos consecutivos o señales externas, pero esa complejidad solo vale la pena si existe evidencia de que mejora el comportamiento.
Llamadas permitidas en Half-Open
Una sola llamada exitosa puede ser insuficiente para declarar recuperación. Cien llamadas de prueba pueden volver a saturar un servicio todavía frágil.
El número debe ser pequeño, pero representativo del tráfico y de las operaciones protegidas.
También importa qué tipo de solicitud se usa como prueba. Una operación liviana no garantiza que el flujo crítico más costoso esté sano.
La configuración depende del tráfico
No existe una configuración universal porque el mismo conjunto de valores produce comportamientos distintos según el volumen.
Dependencias de alto volumen
En una dependencia con miles de llamadas por segundo, una ventana pequeña se llena casi instantáneamente. El circuito puede reaccionar rápido, pero también volverse sensible a picos breves.
Conviene evaluar:
- ventanas temporales;
- umbrales de llamadas lentas;
- segmentación por operación;
- aislamiento de concurrencia;
- impacto de aperturas coordinadas en muchas instancias.
Dependencias de bajo volumen
En una dependencia con pocas llamadas, una ventana grande puede tardar demasiado en reunir la muestra mínima. El circuito podría no abrir durante un fallo evidente.
En estos casos se puede necesitar:
- una ventana menor;
- señales de salud complementarias;
- timeouts estrictos;
- una estrategia manual o externa de aislamiento;
- separar operaciones con perfiles diferentes.
Tráfico con ráfagas
Un pico breve puede llenar una ventana basada en cantidad y dominar la decisión. Una ventana temporal puede representar mejor el periodo de degradación, siempre que se controle la muestra mínima.
Procesamiento asíncrono
En un consumidor de colas, rechazar inmediatamente una llamada puede provocar reentregas, mover mensajes a una cola de errores o acelerar un ciclo de reintentos.
El Circuit Breaker debe coordinarse con:
- visibilidad del mensaje;
- política de reintentos del broker;
- dead-letter queue;
- idempotencia;
- backpressure;
- capacidad de pausar consumidores.
El patrón no puede diseñarse aislado de la semántica del procesamiento.
Circuit Breaker no es Bulkhead, Timeout ni Retry
Estos patrones pueden formar parte de la misma política, pero no son intercambiables.
| Patrón | Decisión principal |
|---|---|
| Timeout | Cuánto tiempo puede esperar un intento |
| Retry | Cuándo vale la pena intentar nuevamente |
| Circuit Breaker | Cuándo dejar de ejecutar temporalmente |
| Bulkhead | Cuánta capacidad puede consumir una dependencia |
| Rate limiting | Cuánta demanda se acepta durante un periodo |
| Deadline | Cuánto tiempo total tiene la operación end-to-end |
Un Circuit Breaker no limita el número de llamadas concurrentes mientras está Closed. Si cien solicitudes reciben permiso al mismo tiempo, las cien pueden llegar a la dependencia. El tamaño de la ventana no es un límite de concurrencia.
Para contener threads, conexiones o tareas en vuelo se necesita un Bulkhead, un pool dedicado, un semáforo, una cola limitada u otro mecanismo equivalente.
Tampoco ejecuta retries. Solo observa resultados y decide si permite nuevas ejecuciones.
La composición y el orden importan porque determinan si el circuito observa cada intento individual o solo el resultado final. Ese problema se desarrolla por separado en Timeout, Retry y Circuit Breaker: cómo combinarlos.
Implementación razonada con Resilience4j
El siguiente ejemplo usa Spring Boot y Resilience4j. Los valores son ilustrativos. No deben copiarse a producción sin medir el comportamiento de la dependencia.
Configuración
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowType: TIME_BASED
slidingWindowSize: 30
minimumNumberOfCalls: 20
failureRateThreshold: 50
slowCallRateThreshold: 50
slowCallDurationThreshold: 2s
permittedNumberOfCallsInHalfOpenState: 5
waitDurationInOpenState: 20s
automaticTransitionFromOpenToHalfOpenEnabled: true
eventConsumerBufferSize: 50
recordExceptions:
- java.io.IOException
- java.util.concurrent.TimeoutException
ignoreExceptions:
- com.example.domain.BusinessRuleException
instances:
catalogService:
baseConfig: default
Esta configuración expresa una política concreta:
- evalúa llamadas de los últimos 30 segundos;
- no calcula tasas hasta reunir al menos 20 ejecuciones;
- abre si 50 % falla;
- también abre si 50 % supera dos segundos;
- permanece 20 segundos en
Open; - usa cinco llamadas para verificar recuperación;
- no castiga al servicio por una regla de negocio ejecutada correctamente.
La configuración todavía está incompleta si no existe un timeout en la llamada protegida. Sin timeout, una ejecución puede permanecer bloqueada mucho tiempo antes de ser registrada como lenta o fallida.
Llamada protegida
@Service
public class CatalogGateway {
private final CatalogClient client;
private final CircuitBreaker circuitBreaker;
public CatalogGateway(
CatalogClient client,
CircuitBreakerRegistry registry) {
this.client = client;
this.circuitBreaker = registry.circuitBreaker("catalogService");
}
public Product findProduct(String productId) {
Supplier<Product> protectedCall = CircuitBreaker.decorateSupplier(
circuitBreaker,
() -> client.findProduct(productId)
);
try {
return protectedCall.get();
} catch (CallNotPermittedException exception) {
throw new CatalogTemporarilyUnavailableException(
"Catalog service is temporarily unavailable",
exception
);
}
}
}
El código distingue dos situaciones:
- La llamada se ejecutó y falló.
- La llamada ni siquiera se ejecutó porque el circuito estaba abierto.
Esa diferencia importa para logs, métricas, respuestas al consumidor y políticas de retry.
Fallback: degradar sin mentir
Un fallback válido depende de la semántica.
Para una consulta de catálogo podría ser aceptable devolver una copia en caché con una marca de antigüedad. Para una orden de pago, confirmar éxito sin ejecutar la operación sería incorrecto.
Un fallback puede:
- devolver datos cacheados;
- reducir el nivel de detalle;
- desactivar una función secundaria;
- responder con indisponibilidad temporal;
- encolar una orden, solo si el contrato permite ejecución diferida.
No debería:
- inventar un resultado exitoso;
- ocultar pérdida de datos;
- transformar silenciosamente una escritura en una lectura antigua;
- impedir que las métricas reflejen el impacto real.
Equivalente conceptual con Polly para .NET
El patrón no depende de Java. Polly modela la misma decisión mediante una proporción de fallos, una duración de muestreo, un throughput mínimo y un periodo de apertura.
var options = new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
MinimumThroughput = 20,
BreakDuration = TimeSpan.FromSeconds(20),
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.HandleResult(response =>
(int)response.StatusCode >= 500)
};
var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddCircuitBreaker(options)
.Build();
Los nombres cambian. Las decisiones son las mismas:
- qué cuenta como fallo;
- durante qué ventana;
- con qué volumen mínimo;
- durante cuánto tiempo se bloquean nuevas ejecuciones.
La biblioteca implementa el mecanismo. La arquitectura sigue siendo responsabilidad del equipo.
Métricas que deben existir
Un Circuit Breaker sin telemetría puede reducir carga y, al mismo tiempo, volver más difícil entender por qué los usuarios reciben rechazos.
Como mínimo se necesita observar:
- estado actual por dependencia;
- transiciones entre estados;
- número y duración de aperturas;
- llamadas exitosas, fallidas e ignoradas;
- llamadas rechazadas porque el circuito estaba abierto;
- failure rate;
- slow call rate;
- duración de las llamadas;
- resultados de las pruebas en
Half-Open; - operación o dependencia protegida;
- impacto sobre la latencia y el error end-to-end.
Con el binder de Micrometer, Resilience4j expone métricas equivalentes a:
resilience4j.circuitbreaker.state
resilience4j.circuitbreaker.calls
resilience4j.circuitbreaker.failure.rate
resilience4j.circuitbreaker.slow.call.rate
resilience4j.circuitbreaker.not.permitted.calls
resilience4j.circuitbreaker.buffered.calls
Los nombres finales pueden transformarse según el backend de métricas, pero la información operativa debe mantenerse.
No conviertas automáticamente cada circuito abierto en una caída total
Un circuito abierto puede significar que una función secundaria está degradada mientras el resto de la aplicación continúa operando.
Si el health check global cambia a DOWN por cualquier circuito abierto, el orquestador podría retirar instancias sanas, provocar reinicios o reducir aún más la capacidad disponible.
La señal de salud debe representar la semántica real:
- ¿la dependencia es crítica para todas las operaciones?
- ¿existe degradación controlada?
- ¿el balanceador debe retirar la instancia?
- ¿se necesita una alerta sin alterar el readiness del proceso?
Health, alerting y autoscaling no deben compartir una interpretación automática de la misma señal.
Puedes profundizar en el diseño de señales en observabilidad con métricas, logs y trazas.
Alertas recomendadas
No toda apertura requiere despertar a una persona. Una apertura breve puede ser exactamente el comportamiento esperado.
Las alertas deberían considerar duración, repetición e impacto.
Circuito abierto durante más de lo esperado
Una apertura que excede el tiempo normal de recuperación indica que la dependencia continúa degradada o que las llamadas de prueba no son representativas.
Aperturas repetidas
Varias transiciones Closed → Open dentro de una ventana pueden revelar inestabilidad, umbrales demasiado sensibles o una dependencia que se recupera solo parcialmente.
Oscilación entre Open y Half-Open
El circuito intenta recuperar tráfico, falla y vuelve a abrir. Esta oscilación puede generar comportamiento intermitente para los consumidores.
Incremento de slow calls
La tasa de llamadas lentas puede anticipar errores, saturación y timeouts. Esperar al error final elimina tiempo de reacción.
Rechazos con impacto funcional
Mil rechazos en una función opcional no equivalen a diez rechazos en una operación crítica. La alerta debe incorporar el flujo afectado y su volumen.
Varios circuitos abiertos simultáneamente
Puede indicar un problema compartido:
- red;
- DNS;
- service mesh;
- base de datos;
- proveedor externo;
- saturación del cliente;
- un cambio de configuración común.
La correlación evita investigar cada circuito como un incidente independiente. Ese mismo criterio ordena cómo investigar circuitos abiertos durante un incidente real: primero el patrón compartido, luego cada dependencia.
Errores comunes
Copiar umbrales de otro sistema
Dos servicios con distinto tráfico, latencia y criticidad no deberían compartir valores solo porque usan la misma biblioteca.
Contabilizar toda excepción como fallo
Una regla de negocio válida puede terminar degradando la tasa técnica y abrir el circuito sin que la dependencia esté enferma.
Abrir con una muestra insuficiente
Pocas llamadas producen decisiones inestables, especialmente en operaciones de bajo volumen.
No proteger llamadas lentas
Un servicio puede devolver 200 OK y aun así destruir el presupuesto de latencia.
Usar un fallback que oculta pérdida de datos
La degradación deja de ser resiliencia cuando rompe el contrato funcional.
Reintentar sin límites antes de registrar el resultado
La dependencia recibe más carga y el Circuit Breaker puede reaccionar demasiado tarde porque solo observa el resultado final de varios intentos.
Compartir un circuito entre dependencias distintas
Si dos endpoints tienen perfiles diferentes, una degradación en uno puede bloquear al otro sin necesidad.
Confundir el tamaño de ventana con concurrencia
Una ventana de 50 llamadas no impide que 500 ejecuciones ocurran en paralelo. Para eso se necesita aislamiento de capacidad.
No instrumentar transiciones
El equipo ve errores, pero no sabe cuándo abrió el circuito, por qué lo hizo ni cuánto tráfico rechazó.
Tratar Open como recuperación
Open solo detiene llamadas. La dependencia sigue fallando hasta que exista evidencia de lo contrario.
Confundir Circuit Breaker con rate limiting
El Circuit Breaker reacciona a la salud observada de una dependencia. El rate limiter controla la cantidad de demanda aceptada. Uno no reemplaza al otro.
Cuándo no usar un Circuit Breaker
El patrón aporta valor cuando existe una dependencia remota, una condición potencialmente persistente y un costo relevante por seguir ejecutando.
No siempre cumple esas condiciones.
Operaciones locales
Una función en memoria que falla por un bug determinístico no se recuperará porque se bloqueen llamadas durante 20 segundos.
Errores no transitorios
Una configuración inválida, un esquema incompatible o una credencial revocada requieren corrección. Abrir y probar periódicamente puede añadir ruido sin cambiar el resultado.
Flujos donde bloquear empeora la consistencia
En algunos procesos coordinados, rechazar una operación intermedia sin una estrategia de compensación puede dejar estados incompletos.
Dependencias con volumen insuficiente
Si nunca existe una muestra representativa, una decisión basada solo en tasas puede ser débil. Tal vez convenga usar timeouts, health signals, controles manuales o un modelo distinto.
Procesos completamente desacoplados
Un flujo basado en colas puede necesitar pausa de consumidores, backpressure, reintentos del broker y dead-letter queues en lugar de un Circuit Breaker tradicional alrededor de cada mensaje.
Cuando la dependencia ya ofrece una semántica de rechazo adecuada
Un SDK o gateway puede incluir throttling, retry hints y control de salud. Añadir otra capa sin comprender la existente puede duplicar políticas y generar interacciones inesperadas.
Marco de decisión
Antes de implementar el patrón, responde:
- ¿La operación cruza una frontera remota o consume un recurso que puede degradarse de forma persistente?
- ¿Seguir llamando aumenta la presión o retiene capacidad escasa?
- ¿Podemos distinguir fallos técnicos de rechazos funcionales?
- ¿Existe suficiente tráfico para evaluar una muestra?
- ¿Qué hará el consumidor cuando la llamada sea rechazada?
- ¿La operación admite fallback, degradación o ejecución diferida?
- ¿Qué timeout limita cada intento?
- ¿Cómo se restringe la concurrencia?
- ¿Qué métricas demostrarán que el circuito protege el sistema?
- ¿Qué condición justificará cerrar nuevamente el circuito?
Si estas preguntas no tienen respuesta, todavía no existe una política de resiliencia. Solo existe una dependencia añadida al proyecto.
Conclusión
Un Circuit Breaker no hace que una dependencia falle menos. Hace que el sistema deje de comportarse como si cada nuevo intento tuviera la misma probabilidad de éxito.
Su valor aparece cuando convierte evidencia operativa en una decisión explícita:
- permitir;
- bloquear;
- probar;
- recuperar;
- volver a bloquear si la recuperación no es real.
La implementación correcta no empieza eligiendo una biblioteca. Empieza definiendo qué se protege, qué resultados importan, cuánta evidencia se necesita y qué comportamiento es seguro durante la degradación.
Después se codifica. Luego se instrumenta. Finalmente se valida en producción.
El caso que mejor lo resume: en una plataforma de pagos de alta disponibilidad, aislar dependencias e introducir circuit breakers con este marco redujo los incidentes críticos en un 70%. La cifra importa menos que su causa: los fallos dejaron de propagarse.
Referencias técnicas
- Resilience4j — CircuitBreaker: resilience4j.readme.io/docs/circuitbreaker
- Resilience4j — Spring Boot 2 y 3: resilience4j.readme.io/docs/getting-started-3
- Resilience4j — Micrometer metrics: resilience4j.readme.io/docs/micrometer
- Polly — Circuit breaker resilience strategy: pollydocs.org/strategies/circuit-breaker
- Microsoft Azure Architecture Center — Circuit Breaker pattern: learn.microsoft.com/azure/architecture/patterns/circuit-breaker