Circuit Breaker
How to protect your services from cascading failures using the Circuit Breaker pattern with states, fallbacks, and automatic recovery.
What problem it solves
In a microservices architecture, services depend on one another. When a downstream service becomes slow or stops responding, the services calling it accumulate open connections, exhaust their thread pools, and eventually fail as well. This domino effect is known as a cascading failure.
Example of the problem
Usuario β API Gateway β Servicio A β Servicio B (lento/caΓdo)
β³ timeout 30s
β³ timeout 30s
β³ timeout 30s
β
Servicio A agota sus threads
esperando respuesta de B
β
API Gateway agota sus threads
esperando respuesta de A
β
Todo el sistema deja de responder
Without protection, a single slow service can take down the entire platform. The Circuit Breaker acts as an automatic switch that cuts off communication with a failing service, preventing the problem from spreading.
The pattern was popularized by Michael Nygard in his book Release It! and takes its name from the electrical circuit breakers that cut the circuit when they detect an overload.
Real impact of cascading failures
| Metric | Without Circuit Breaker | With Circuit Breaker |
|---|---|---|
| Detection time | Minutes (when users report it) | Seconds (circuit state change) |
| Affected services | All that depend on the failed service | Only the failed service |
| Recovery | Manual (restart services, clear queues) | Automatic (half-open detects recovery) |
| User experience | Long timeouts, 500 errors | Fast response with fallback or clear error |
| Resources consumed | Threads and connections exhausted | Resources released immediately |
How it works
The Circuit Breaker is a proxy placed between a service and its dependencies. It monitors the calls and, based on the failure rate, decides whether to allow or block requests.
The three states
Fallos > umbral
ββββββββββββ βββββββββββββββββββΊ ββββββββββββ
β CERRADO β β ABIERTO β
β (normal) β βββββββββββββββββ β (cortado)β
ββββββββββββ Prueba exitosa ββββββ¬ββββββ
β² β
β Timeout expira β
β βΌ
β βββββββββββββ
βββββββ Prueba exitosa βββββSEMI-ABIERTOβ
βββββββββββββ
Prueba falla βββββΊ ABIERTO
Closed state
This is the normal state. All requests pass through to the downstream service. The circuit breaker monitors the results:
- It counts recent failures (errors, timeouts)
- If the failure rate exceeds a threshold (e.g., 50% failures in the last 10 calls), it switches to Open
Open state
The circuit is cut. Requests do not reach the downstream service. Instead, the circuit breaker responds immediately with an error or executes a fallback.
- It avoids sending traffic to a service we know is failing
- It releases resources (threads, connections) that would be wasted waiting for timeouts
- After a wait period (e.g., 30 seconds), it switches to Half-Open
Half-Open state
A test state. The circuit breaker lets a limited number of requests through to check whether the service has recovered:
- If the test requests succeed β it returns to Closed
- If the test requests fail β it returns to Open
Configuration parameters
| Parameter | Description | Example | Consideration |
|---|---|---|---|
| Failure threshold | Percentage of failures to open | 50% | Too low = false positives, too high = slow reaction |
| Request volume | Minimum requests before evaluating | 10 requests | Prevents opening the circuit on 1 isolated failure |
| Sleep window | Time in the open state | 30 seconds | Should give the service time to recover |
| Success threshold | Successes to close the circuit | 3 consecutive | Higher = more confidence in the recovery |
| Timeout | Maximum wait time | 5 seconds | Should be less than the client timeout |
Fallback strategies
When the circuit is open, instead of returning a raw error, you can implement fallbacks:
| Strategy | Description | Example |
|---|---|---|
| Default value | Return static data | Show cached catalog |
| Cache | Last successful cached response | Product price from 5 min ago |
| Alternative service | Redirect to a backup | Use secondary payment service |
| Graceful degradation | Reduced functionality | Show βprice not availableβ |
| Fail fast | Immediate error with a clear message | HTTP 503 with retry-after header |
Full flow example
Estado: CERRADO
Llamada 1 β Servicio B β β
OK
Llamada 2 β Servicio B β β
OK
Llamada 3 β Servicio B β β Timeout
Llamada 4 β Servicio B β β Error 500
Llamada 5 β Servicio B β β Timeout
β Tasa de fallos: 60% > umbral 50%
β Cambio a ABIERTO β‘
Estado: ABIERTO
Llamada 6 β β‘ Fallback inmediato (no llega a B)
Llamada 7 β β‘ Fallback inmediato
... (30 segundos de espera)
β Cambio a SEMI-ABIERTO
Estado: SEMI-ABIERTO
Llamada 8 β Servicio B β β
OK (prueba)
Llamada 9 β Servicio B β β
OK (prueba)
Llamada 10 β Servicio B β β
OK (prueba)
β 3 Γ©xitos consecutivos
β Cambio a CERRADO β
Practical example: Circuit Breaker in a payment service
Imagine an order service that calls an external payment provider:
// PseudocΓ³digo de configuraciΓ³n del Circuit Breaker
circuitBreaker = new CircuitBreaker({
name: "payment-provider",
failureThreshold: 50, // 50% de fallos
requestVolume: 10, // mΓnimo 10 requests para evaluar
sleepWindow: 30000, // 30 segundos en estado abierto
successThreshold: 3, // 3 Γ©xitos para cerrar
timeout: 5000, // 5 segundos de timeout por llamada
fallback: () => ({
status: "PENDING",
message: "Pago en cola, se procesarΓ‘ cuando el servicio se recupere"
})
});
// Uso
async function procesarPago(pedidoId, monto) {
try {
resultado = await circuitBreaker.execute(() =>
paymentProvider.charge(pedidoId, monto)
);
return resultado;
} catch (error) {
if (error instanceof CircuitOpenError) {
// El circuito estΓ‘ abierto, se ejecutΓ³ el fallback
encolarPagoParaReintento(pedidoId, monto);
return error.fallbackResult;
}
throw error;
}
}
Monitoring the Circuit Breaker
Circuit state changes are valuable signals for the operations team:
// MΓ©tricas a exponer
circuit_breaker_state{name="payment-provider"} = "closed" | "open" | "half_open"
circuit_breaker_calls_total{name="payment-provider", result="success"}
circuit_breaker_calls_total{name="payment-provider", result="failure"}
circuit_breaker_calls_total{name="payment-provider", result="rejected"}
circuit_breaker_state_transitions_total{name="payment-provider", from="closed", to="open"}
// Alertas recomendadas
ALERT CircuitBreakerOpen
IF circuit_breaker_state == "open"
FOR 1m
LABELS { severity = "warning" }
ANNOTATIONS { summary = "Circuit breaker abierto para payment-provider" }
Advantages
- Prevents cascading failures: A downed service does not drag the rest of the system down
- Fail fast: Requests fail immediately instead of waiting for long timeouts
- Resource release: No threads or connections are wasted on calls that are going to fail
- Automatic recovery: The half-open state detects when the service recovers
- Improved user experience: Fallbacks offer degraded responses instead of errors
- Observability: State changes are clear signals for alerting and monitoring
Trade-offs / Disadvantages
- Delicate configuration: Incorrect thresholds can open the circuit too early or too late
- Complexity in fallbacks: Designing useful fallbacks for each dependency takes effort
- Stale data: Cache-based fallbacks may return outdated information
- Complex testing: Simulating the three states and the transitions requires infrastructure
- Recovery latency: The sleep window introduces a delay between the actual recovery and its detection
- Does not fix the root cause: The circuit breaker protects the system, but the downstream service still needs fixing
When to use
- Calls to external services or microservices that can fail or become slow
- Systems where a failure in one dependency must not affect overall availability
- When you need to protect shared resources (thread pools, connection pools)
- APIs that call third-party services with variable SLAs
- Systems with high-availability requirements where graceful degradation is preferable
- When you already have metrics and alerts to monitor the state of the circuits
When to avoid
- Calls to local resources (local database, files) where the overhead is not justified
- Operations that must complete no matter what (with no possible fallback alternative)
- Simple systems with few external dependencies
- When the additional latency of the proxy is unacceptable
- Asynchronous calls where you already have queues with retries and dead letter queues
- Prototypes or MVPs where resilience is not a priority
Common technologies and implementations
| Category | Options |
|---|---|
| Libraries | Resilience4j (Java), Polly (.NET), Hystrix (Java, legacy), opossum (Node.js) |
| Service Mesh | Istio, Linkerd, Envoy (circuit breaking at the infrastructure level) |
| Cloud | AWS App Mesh, Azure Traffic Manager |
| Monitoring | Prometheus + Grafana, Datadog, New Relic |
| Complementary patterns | Retry with backoff, Bulkhead, Timeout, Rate Limiting |
Relationship with other patterns
The Circuit Breaker is rarely used on its own. It works best in combination with other resilience patterns:
βββββββββββββββββββββββββββββββββββββββββββ
β Cadena de resiliencia β
β β
β Solicitud β
β β β
β βΌ β
β ββββββββββββ β
β β Timeout β Limita tiempo de espera β
β ββββββ¬ββββββ β
β βΌ β
β ββββββββββββ β
β β Retry β Reintenta con backoff β
β ββββββ¬ββββββ β
β βΌ β
β ββββββββββββ β
β β Circuit β Corta si hay muchos β
β β Breaker β fallos consecutivos β
β ββββββ¬ββββββ β
β βΌ β
β ββββββββββββ β
β β Bulkhead β AΓsla recursos por β
β β β dependencia β
β ββββββ¬ββββββ β
β βΌ β
β Servicio downstream β
βββββββββββββββββββββββββββββββββββββββββββ
- Timeout Pattern: Defines how long to wait before considering a call as failed. The timeout feeds the circuit breaker with failure data
- Retry Pattern: Retries failed calls with exponential backoff. Retries happen before the circuit opens; once open, no retry occurs
- Bulkhead: Isolates resources (threads, connections) per dependency so that a slow service does not consume all the systemβs resources
- Saga Pattern: When a saga step fails and the circuit breaker is open, the saga triggers compensations instead of retrying indefinitely
- API Gateway: The gateway can implement circuit breakers for each backend service, centralizing the protection
- Idempotency Pattern: When the circuit closes and operations are retried, idempotency guarantees that effects are not duplicated
Common mistakes
| Mistake | Consequence | Solution |
|---|---|---|
| Threshold too low | Circuit opens on isolated failures | Increase the minimum request volume |
| Sleep window too short | Circuit oscillates between open and closed | Give more recovery time |
| No fallback defined | Raw error to the user when the circuit opens | Design fallbacks for each dependency |
| A single global circuit breaker | One slow service affects everyone | One circuit breaker per dependency |
| Not monitoring transitions | You donβt detect problems in time | Alerts on state changes |
| Ignoring the half-open state | Excessive traffic during testing | Limit the test requests |
Next steps
The Circuit Breaker is a fundamental piece of resilience in microservices. To round out your understanding of resilience patterns, explore the Retry Pattern for handling transient failures with backoff, the Timeout Pattern for limiting waits, and the Bulkhead Pattern for isolating resources per dependency.