Circuit Breaker: qué es, cómo funciona y cómo implementarlo en producción

Un Circuit Breaker mal configurado no protege el sistema. Solo cambia una caída lenta por una secuencia de rechazos difíciles de explicar.

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:

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:

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:

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.

Máquina de estados del Circuit Breaker Máquina de estados de un Circuit Breaker con transiciones entre Closed, Open y Half-Open según tasas de fallo, tiempo de espera y llamadas de prueba. Closed Permite tráfico · registra resultados Open Rechaza llamadas · protege capacidad Half-Open Permite pocas pruebas · valida tasa de fallos o llamadas lentas ≥ umbral + muestra termina la espera (wait duration) pruebas OK · recuperación pruebas fallan · recaída
Máquina de estados del Circuit Breaker. Cuatro transiciones definen su comportamiento: la condición de apertura (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:

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:

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:

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:

Normalmente no deberían contar de la misma forma:

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:

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:

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:

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:

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:

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ónDecisión principal
TimeoutCuánto tiempo puede esperar un intento
RetryCuándo vale la pena intentar nuevamente
Circuit BreakerCuándo dejar de ejecutar temporalmente
BulkheadCuánta capacidad puede consumir una dependencia
Rate limitingCuánta demanda se acepta durante un periodo
DeadlineCuá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

YAML
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:

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

Java
@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:

  1. La llamada se ejecutó y falló.
  2. 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:

No debería:

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.

C#
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:

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:

Con el binder de Micrometer, Resilience4j expone métricas equivalentes a:

Métricas
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:

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:

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:

  1. ¿La operación cruza una frontera remota o consume un recurso que puede degradarse de forma persistente?
  2. ¿Seguir llamando aumenta la presión o retiene capacidad escasa?
  3. ¿Podemos distinguir fallos técnicos de rechazos funcionales?
  4. ¿Existe suficiente tráfico para evaluar una muestra?
  5. ¿Qué hará el consumidor cuando la llamada sea rechazada?
  6. ¿La operación admite fallback, degradación o ejecución diferida?
  7. ¿Qué timeout limita cada intento?
  8. ¿Cómo se restringe la concurrencia?
  9. ¿Qué métricas demostrarán que el circuito protege el sistema?
  10. ¿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:

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

Jorel del Portal

Jorel del Portal

Jorel del Portal es ingeniero de sistemas especializado en arquitectura, integración, resiliencia y observabilidad de plataformas críticas. Diseña sistemas, construye productos y documenta decisiones reales de ingeniería.