Resilience Patterns · Core Resilience Patterns
Circuit Breakers
Stop calling a failing dependency and let it recover.
A circuit breaker wraps calls to a dependency and tracks failures (errors, timeouts, slow calls). Closed: calls pass through. When the failure rate in a sliding window crosses a threshold it opens: calls fail immediately (or use a fallback) for a cool-down period. Then half-open: a few trial calls decide whether to close again. Breakers fail fast, protect callers' resources and give dependencies room to recover.
Circuit breaker states
| State | Behavior | Transition |
|---|---|---|
| Closed | Calls pass; failures counted | → Open when failure/slow rate ≥ threshold |
| Open | Fail fast / fallback | → Half-open after wait duration |
| Half-open | Limited trial calls | → Closed if trials succeed, else Open |
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.slidingWindowType(SlidingWindowType.COUNT_BASED).slidingWindowSize(50)
.failureRateThreshold(50).slowCallDurationThreshold(Duration.ofMillis(500)).slowCallRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30)).permittedNumberOfCallsInHalfOpenState(5)
.build();
CircuitBreaker breaker = CircuitBreaker.of("recommendations", config);
Supplier<List<Product>> guarded = CircuitBreaker.decorateSupplier(breaker, () -> recs.forUser(userId));
List<Product> items = Try.ofSupplier(guarded).recover(t -> popular.cached()).get();
import pybreaker
recs_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=30)
def recommendations(user_id: str) -> list[Product]:
try:
return recs_breaker.call(recs_client.for_user, user_id)
except (pybreaker.CircuitBreakerError, RecsTimeout):
return popular_products_cached() # fallback while open
import CircuitBreaker from "opossum";
const breaker = new CircuitBreaker((userId: string) => recsClient.forUser(userId), {
timeout: 500, // count slow calls as failures
errorThresholdPercentage: 50,
resetTimeout: 30_000,
volumeThreshold: 20, // minimum calls before tripping
});
breaker.fallback(() => popularProducts.cached());
breaker.on("open", () => metrics.increment("breaker.open", { dep: "recommendations" }));
const items = await breaker.fire(userId);
Common pitfalls
- Breakers without timeouts (slow calls never count as failures).
- One breaker for all endpoints of a dependency (one bad endpoint trips everything).
- No metrics/alerts on breaker state.
Key points
- Fail fast when a dependency is unhealthy
- Count slow calls, not only errors
- Pair with fallbacks and monitoring
Further reading: Martin Fowler: Circuit Breaker
Practice questions
Why should slow calls count toward opening a circuit breaker?
Answer
A dependency that responds slowly exhausts caller resources just like failures; counting slow calls lets the breaker protect against latency-based degradation.
What is the purpose of the half-open state?
Answer
To test with limited traffic whether the dependency has recovered before resuming full traffic, avoiding a thundering herd on recovery.
Open in the app to mark lessons complete, take notes and track your progress.