Learn how to prevent cascading failures from taking down your entire system. Master the pattern that stops one failing service from collapsing everything else.
Save
Complete lesson & earn 250 PX
EXERCISE
1A slow database can bring down your entire system through cascading failures. Understanding this problem is critical to building resilient systems.
Save
EXERCISE
2Circuit breakers detect when services are unhealthy and prevent calls to them. This stops cascading failures and keeps partial functionality alive.
Save
EXERCISE
3Circuit breakers require tracking service health and enforcing checks. Understanding implementation helps you use them effectively.
Save
EXERCISE
4When circuit breaker opens, your service needs fallback behavior. Choosing the right fallback strategy determines user experience during failures.
Save
EXERCISE
5Circuit breakers need proper configuration and monitoring. Wrong thresholds cause false positives or miss real failures.
Save
EXERCISE
6Large systems use circuit breakers extensively. Understanding production patterns and tools helps you implement them effectively.
Save
You built a social media feed. When users open the app, they see:
Service Dependencies:
Feed Service
├─ Recommendation Service → Profile Service → Profile DB
├─ Trending Service → Post Service → Post DB
└─ Post Service → Profile Service → Profile DB
Multiple services depend on Profile Service. Profile Service depends on Profile DB.
Everything works smoothly... until it does not.
Profile DB gets overwhelmed. Maybe traffic spiked. Maybe a bad query locked tables. Maybe disk is full.
What happens next?
Step 1: Profile DB slows down. Queries taking 5 seconds instead of 50ms.
Step 2: Profile Service waits for DB responses. HTTP connections stay open longer.
Step 3: Profile Service has limited connections (say 1000 max). Slow responses mean connections pile up. New requests queue.
Step 4: Services calling Profile Service (Recommendation, Post) now wait. Their response times increase.
Step 5: HTTP has timeouts (typically 30 seconds). Requests exceeding timeout fail.
Step 6: Failed requests retry. More load on already struggling Profile Service.
Step 7: Profile Service completely overwhelmed. All requests timing out.
Step 8: Recommendation Service cannot get profiles. Trending Service cannot get post data. Everything dependent on Profile Service fails.
Step 9: Feed Service tries to call Recommendation and Trending. Both failing. Feed Service also fails.
Complete outage. One slow took down the entire application.
Open Connections: Every HTTP request holds a connection. Servers have connection limits.
Slow = Connection Hogging: When services slow down, connections stay open longer. Connection pool exhausts.
Timeouts: Eventually requests timeout, but damage is done. Pile-up already happened.
Retry Storms: Failed requests retry, amplifying the problem.
Profile DB slows 10x → Profile Service slows 10x → Dependent services slow 10x → Timeout failures increase 100x → Retry attempts increase 1000x
Slowness amplifies exponentially through the system.
AWS S3 Outage (2017): One S3 subsystem slowed. Services dependent on S3 slowed. Services dependent on those services slowed. Cascaded through entire AWS ecosystem. Took hours to recover.
Your application: Without circuit breakers, one database slowdown can trigger complete collapse within minutes.
Transitive Dependencies: Service A depends on B depends on C depends on D. Failure in D cascades through C, B, A.
No Isolation: One failing service brings down unrelated .
Users see: Complete outage. Even features that could work independently are down.
This is unacceptable. Circuit breakers solve this.
Before making a call to another service, check: Is that service healthy?
If healthy: Make the call normally.
If unhealthy: Skip the call. Return default value or gracefully degrade.
Simple concept. Massive impact.
Scenario: Profile DB is slow. Profile Service is struggling.
Without Circuit Breaker:
Recommendation Service keeps calling Profile Service → Requests timeout → Recommendation Service overwhelmed → Feed Service fails
With Circuit Breaker:
Result: Partial functionality maintained. No cascading failure. Users can still use the app.
Closed (Normal Operation):
Service is healthy. Requests flow through normally. Circuit breaker monitors for failures.
Open (Service Failed):
Too many failures detected. Circuit breaker "opens." All requests immediately fail without calling the service. This gives the failing service time to recover.
Half-Open (Testing Recovery):
After timeout period, circuit breaker allows a few test requests. If they succeed, circuit closes. If they fail, circuit reopens.
CLOSED → (too many failures) → OPEN
OPEN → (timeout expires) → HALF-OPEN
HALF-OPEN → (test requests succeed) → CLOSED
HALF-OPEN → (test requests fail) → OPEN
This automatic recovery testing prevents manual intervention.
Netflix: Extensive use of circuit breakers (Hystrix library). When recommendation service fails, Netflix still shows your watch history and popular titles.
Uber: If surge pricing service fails, rides still work. Pricing falls back to normal rates. Circuit breaker prevents pricing issues from breaking entire ride flow.
E-commerce: If product recommendations fail, checkout still works. Users can complete purchases even when auxiliary services are down.
Profile Service Down:
Recommendation Service Down:
Payment Service Down:
Each service degraded gracefully. Core functionality survives.
Blast radius: How much damage one failure causes.
Without circuit breakers: One DB failure takes down entire application. Blast radius = 100%.
With circuit breakers: One service failure affects only dependent features. Blast radius = 10-20%.
Users still accomplish critical tasks. Partial outage beats total outage.
Core Components:
CREATE TABLE circuit_breaker_status (
service_name VARCHAR(100) PRIMARY KEY,
is_healthy BOOLEAN NOT NULL,
last_checked TIMESTAMP,
failure_count INTEGER DEFAULT 0
);
-- Example data
INSERT INTO circuit_breaker_status VALUES
('profile-service', true, NOW(), 0),
('recommendation-service', true, NOW(), 0),
('payment-service', false, NOW(), 5);
Simple key-value store. Service name → Health status.
Before Circuit Breaker (Naive approach):
async function getRecommendations(userId) {
// Directly call service
const profiles = await profileService.getProfiles(userIds);
return processRecommendations(profiles);
}
With Circuit Breaker:
async function getRecommendations(userId) {
// Check circuit breaker first
const isHealthy = await circuitBreaker.check('profile-service');
if (isHealthy) {
// Service healthy, make the call
const profiles = await profileService.getProfiles(userIds);
return processRecommendations(profiles);
} else {
// Service unhealthy, use fallback
console.log('Profile service down, using default profiles');
const defaultProfiles = getDefaultProfiles();
return processRecommendations(defaultProfiles);
}
}
Every service call protected by circuit breaker check.
Simple approach: Human flips the switch.
Process:
UPDATE circuit_breaker_status SET is_healthy = false WHERE service_name = 'profile-service'UPDATE ... SET is_healthy = trueAdvantages: Simple, no automation complexity, full control.
Disadvantages: Requires human intervention, slower response.
Best for: Starting out, smaller teams, learning the pattern.
Advanced approach: System detects failures automatically.
Failure Detection:
async function callServiceWithCircuitBreaker(serviceName, request) {
try {
const response = await makeRequest(request);
// Success! Reset failure count
await circuitBreaker.recordSuccess(serviceName);
return response;
} catch (error) {
// Failure! Increment failure count
const failures = await circuitBreaker.recordFailure(serviceName);
// Too many failures? Open circuit
if (failures > FAILURE_THRESHOLD) {
await circuitBreaker.openCircuit(serviceName);
}
throw error;
}
}
Logic:
Advantages: No human needed, instant response, automatic recovery.
Disadvantages: Complex, can open unnecessarily, requires tuning.
Best for: Mature systems, large scale, experienced teams.
{
"profile-service": {
"failureThreshold": 5,
"timeout": 30,
"retryAfter": 60
},
"payment-service": {
"failureThreshold": 3,
"timeout": 10,
"retryAfter": 120
}
}
Different services get different thresholds based on criticality.
Payment service: Low tolerance (3 failures). Long recovery (120s).
Profile service: Higher tolerance (5 failures). Fast recovery (60s).
Issue: Every request queries circuit breaker DB. That DB becomes bottleneck!
Solution: Cache circuit breaker status in each .
Implementation:
Each API server caches: {"profile-service": true, "payment-service": false}
Check cache instead of DB on every request.
Problem: Cache becomes stale. How do servers know status changed?
Solution: PubSub! (We learned this earlier!)
Flow:
UPDATE circuit_breaker_status SET is_healthy = false WHERE service_name = 'profile-service'PUBLISH circuit-breaker-updates '{"service": "profile-service", "healthy": false}'Result: Near-instant propagation. No DB bottleneck. Scalable solution.
This combines multiple patterns: Circuit breakers + + PubSub. Beautiful!
When to use: Service provides non-critical enhancement.
Example: Profile pictures in recommendations.
Fallback:
if (circuitBreaker.isOpen('profile-service')) {
// Use default avatar
return {
userId: user.id,
username: user.username,
avatar: '/assets/default-avatar.png' // Default image
};
}
User experience: Sees generic avatar instead of personalized one. Barely notices.
When to use: Recent data is acceptable.
Example: Product recommendations.
Fallback:
if (circuitBreaker.isOpen('recommendation-service')) {
// Serve cached recommendations from yesterday
return cache.get(`recommendations:${userId}`);
}
User experience: Sees slightly stale recommendations. Still useful.
When to use: Partial data is better than no data.
Example: User profiles with social connections.
Fallback:
if (circuitBreaker.isOpen('social-graph-service')) {
// Return profile without friend count and mutual friends
return {
name: user.name,
bio: user.bio,
// Skip: friendCount, mutualFriends (requires failed service)
};
}
User experience: Profile loads but missing some details. Core info present.
When to use: Action can be delayed.
Example: Sending email notifications.
Fallback:
if (circuitBreaker.isOpen('email-service')) {
// Queue for later processing
await queue.push('email-queue', {
to: user.email,
subject: 'Welcome!',
body: emailBody,
retryAt: Date.now() + 3600000 // Retry in 1 hour
});
return { status: 'queued' };
}
User experience: No immediate email. Receives it when service recovers. User likely does not notice delay.
When to use: Backup service available.
Example: Primary payment gateway down.
Fallback:
if (circuitBreaker.isOpen('stripe-payment')) {
// Use backup payment processor
return processPaymentWithPayPal(paymentDetails);
}
User experience: Payment succeeds. User does not know which processor was used.
When to use: Cannot proceed without the service.
Example: Payment processing has no alternatives.
Fallback:
if (circuitBreaker.isOpen('payment-service')) {
throw new ServiceUnavailableError(
'Payment processing temporarily unavailable. Please try again in a few minutes.'
);
}
User experience: Clear error message. Knows to retry later. Better than hanging or cryptic errors.
Questions to ask:
Critical payment processing → Fail fast with message
Nice-to-have recommendations → Default values or cached data
Non-urgent notifications → Queue for later
Profile pictures → Default avatars
Real applications use multiple strategies simultaneously.
E-commerce checkout:
async function processCheckout(cart, user) {
let recommendations = [];
// Try to get recommendations, fallback to defaults
if (circuitBreaker.isClosed('recommendation-service')) {
recommendations = await getRecommendations(user);
} else {
recommendations = getPopularProducts(); // Fallback
}
// Payment must succeed, no fallback
if (circuitBreaker.isOpen('payment-service')) {
throw new Error('Payment service unavailable');
}
const payment = await processPayment(cart.total);
// Email can be queued
if (circuitBreaker.isOpen('email-service')) {
await queueEmail(user, orderDetails);
} else {
await sendEmail(user, orderDetails);
}
return { orderId, payment, recommendations };
}
Core flow: Payment must work. Everything else degrades gracefully.
With good fallbacks: Users barely notice service failures. Maybe slight delay or missing non-critical info.
Without fallbacks: Complete application failure. Users cannot do anything.
Example: Amazon. Recommendations fail? You still shop. Reviews fail? You still checkout. Payment succeeds? Order goes through. Only critical path must work.
Failure Threshold: How many failures before opening circuit?
Too low (2 failures) → Opens unnecessarily during minor hiccups
Too high (50 failures) → Takes too long to open, damage already done
Sweet spot: 5-10 failures for most services
Timeout Window: Time period to count failures?
Too short (10 seconds) → Misses slow degradation
Too long (10 minutes) → Reacts too slowly
Sweet spot: 30-60 seconds for most services
Recovery Timeout: How long before testing recovery?
Too short (10 seconds) → Tests before service recovers, keeps circuit open
Too long (10 minutes) → Service recovered but circuit stays open unnecessarily
Sweet spot: 60-120 seconds for most services
Example Configuration:
const circuitBreakerConfig = {
'profile-service': {
failureThreshold: 5, // Open after 5 failures
timeoutWindow: 30, // Within 30 seconds
recoveryTimeout: 60, // Test recovery after 60s
halfOpenRequests: 3 // Send 3 test requests when half-open
},
'payment-service': {
failureThreshold: 3, // More critical, lower threshold
timeoutWindow: 20,
recoveryTimeout: 120, // Longer recovery time
halfOpenRequests: 5
}
};
Key metrics to track:
Circuit State: Is circuit currently open, closed, or half-open?
Open Count: How many times did circuit open today?
Open Duration: How long was circuit open each time?
Fallback Usage: How often are fallbacks triggered?
Recovery Success Rate: When circuit tests recovery, how often does it succeed?
Service: profile-service
├─ Current State: CLOSED ✓
├─ Opens Today: 2
├─ Total Open Time: 15 minutes
├─ Fallback Triggers: 127
└─ Last Recovery Test: Success (2 min ago)
Service: payment-service
├─ Current State: OPEN ⚠️
├─ Opens Today: 1
├─ Total Open Time: 8 minutes (ongoing)
├─ Fallback Triggers: 43
└─ Next Recovery Test: in 52 seconds
At a glance: Payment service currently down. Profile service had brief issues but recovered.
Alert on circuit opening:
ALERT: Payment Service Circuit OPENED
Time: 2:14 PM
Failure Count: 5 in last 30 seconds
Impact: All payment processing using fallback
Action: Investigate payment service immediately
Track recovery:
RESOLVED: Payment Service Circuit CLOSED
Open Duration: 12 minutes
Recovery Test: Successful
Impact: Payment processing resumed normally
Mistake 1: Same threshold for all services
Not all services are equal. Critical services need lower thresholds.
Fix: Configure per-service thresholds based on criticality.
Mistake 2: Testing recovery too quickly
Circuit opens, immediately tests recovery. Service still struggling. Circuit reopens. Repeat.
Fix: Longer recovery timeout. Give service time to actually recover.
Mistake 3: No
Circuits opening and closing. Team does not know.
Fix: Alerts on circuit state changes. Dashboard showing current state.
Mistake 4: Forgetting to close circuits manually
Service recovered. Circuit still open because recovery test failed once.
Fix: Manual override option. Admin can force circuit closed after verifying service health.
Pattern 1: Circuit opens frequently
Service is flaky. Either fix service or raise threshold if failures are transient.
Pattern 2: Circuit stays open long
Service takes time to recover. Increase recovery timeout.
Pattern 3: Recovery tests always fail
Recovery timeout too short. Service needs more time.
Pattern 4: Circuit never opens despite service issues
Threshold too high. Lower it.
Simulate failures:
Test during low traffic to avoid affecting real users.
Too sensitive → False positives, unnecessary degradation
Too lenient → Misses real failures, cascading collapse
Tuning circuit breakers is iterative. Start conservative. Adjust based on real-world behavior. Monitor and refine continuously.
The goal: Open when needed. Close when recovered. Keep users happy.
Netflix pioneered circuit breaker patterns at massive scale.
Hystrix (now in maintenance mode) was their circuit breaker library. Handled billions of requests daily across thousands of services.
Key features:
Circuit breaking: Automatic failure detection and circuit opening
Fallbacks: Built-in fallback mechanism
Real-time monitoring: Dashboard showing all circuit states
Thread pool isolation: Each service call uses separate thread pool, preventing one slow service from exhausting all threads
Metrics: Detailed success/failure/timeout tracking
Resilience4j (Java): Hystrix successor. Lighter weight, functional approach.
Polly (.NET): Circuit breakers for C# applications.
Opossum (): Circuit breaker for /Node backends.
Istio (Service Mesh): Circuit breaking at infrastructure level, not application code.
All provide similar functionality: automatic failure detection, fallbacks, monitoring.
Traditional: Each service implements circuit breaker logic in code.
Service Mesh (Istio, Linkerd): Circuit breakers in infrastructure layer.
Benefits:
Language agnostic: Works regardless of programming language
Centralized configuration: One place to manage all circuit breakers
Automatic application: No code changes needed in services
Consistent behavior: All services use same circuit breaker logic
Example Istio configuration:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: profile-service-circuit-breaker
spec:
host: profile-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 60s
Translation: After 5 consecutive errors within 30 seconds, stop sending traffic for 60 seconds.
All services calling profile-service get this protection automatically.
Large systems use circuit breakers at multiple levels.
Application Layer:
Service A → [Circuit Breaker] → Service B
Application code checks circuit breaker before making call.
Layer:
Client → [API Gateway with Circuit Breaker] → Service A → Service B
API Gateway protects backend from excessive traffic during failures.
Infrastructure Layer (Service Mesh):
Service A → [Sidecar Proxy with Circuit Breaker] → Service B
Traffic intercepted at network level, circuit breaker applied transparently.
Amazon:
Circuit breakers protect checkout flow. If recommendation service fails, checkout still works. Users complete purchases without recommendations.
Uber:
If surge pricing calculation fails, rides still happen at base price. Circuit breaker prevents pricing issues from blocking rides.
Twitter:
If timeline ranking service fails, show chronological timeline. Circuit breaker ensures users still see tweets, just without personalization.
Challenge: In , multiple instances of Service A call Service B.
Question: Should circuit breakers be per-instance or global?
Per-Instance Circuit Breakers:
Each Service A instance has own circuit breaker. Instance 1 might open circuit while Instance 2 keeps calling.
Advantage: Localized failure handling
Disadvantage: Service B still gets hammered by other instances
Global Circuit Breakers:
Shared circuit breaker state across all Service A instances. When one instance opens circuit, all instances stop calling.
Advantage: Complete protection for Service B
Disadvantage: Requires coordination (shared database or cache)
Hybrid Approach:
Per-instance circuit breakers + global trip mechanism. Any instance can trigger global "Service B is down" signal.
Early days (pre-2010): Manual service disable flags. Engineers manually flipped switches.
Netflix era (2012-2018): Automated circuit breakers in application code. Hystrix popularized the pattern.
Service mesh era (2018-present): Circuit breakers in infrastructure. Configuration over code.
Future: ML-based adaptive circuit breakers. Automatically tune thresholds based on historical patterns.
Start simple: Manual flags in database. Graduate to automation.
Monitor everything: Know when circuits open, how long they stay open, when they recover.
Test regularly: Chaos engineering. Intentionally break services to verify circuit breakers work.
Tune continuously: Adjust thresholds based on real behavior, not guesses.
Combine patterns: Circuit breakers + retries + timeouts + fallbacks = resilient system.
Circuit breakers are not silver bullets. They are one tool in your resilience toolkit. Combined with , health checks, retries, and monitoring, they create systems that gracefully handle failures instead of collapsing completely.
When one service fails, it can trigger a domino effect that crashes everything. Circuit breakers detect failing services and stop requests to them, preventing cascade failures and keeping your system partially operational.