patternjavascriptMajor
Circuit breaker pattern: fail fast when dependencies are down
Viewed 0 times
circuit breakerfail fastresiliencecascading failureOPEN CLOSED HALF_OPENfault tolerance
Error Messages
Problem
When a downstream service is down, every request to your service waits for timeout before failing, exhausting your thread pool or connection pool and taking down your service too.
Solution
Implement a circuit breaker with three states:
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.successThreshold = options.successThreshold || 2;
this.timeout = options.timeout || 60000; // ms to wait before half-open
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failureCount = 0;
this.successCount = 0;
this.nextAttempt = Date.now();
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN — call rejected');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (err) {
this.onFailure();
throw err;
}
}
onSuccess() {
this.failureCount = 0;
if (this.state === 'HALF_OPEN') {
this.successCount++;
if (this.successCount >= this.successThreshold) {
this.state = 'CLOSED';
this.successCount = 0;
}
}
}
onFailure() {
this.failureCount++;
this.successCount = 0;
if (this.failureCount >= this.failureThreshold || this.state === 'HALF_OPEN') {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
const paymentBreaker = new CircuitBreaker({ failureThreshold: 3, timeout: 30000 });
// Usage
try {
const result = await paymentBreaker.call(() => paymentService.charge(amount));
} catch (err) {
if (err.message.includes('Circuit breaker')) {
return { error: 'Payment service temporarily unavailable' };
}
throw err;
}Why
OPEN state rejects calls immediately (fail fast) instead of waiting for timeout. HALF_OPEN state probes the downstream service to see if it has recovered.
Gotchas
- Circuit breakers should be per downstream service and per client instance — don't share state across requests
- Expose circuit breaker state in your health check endpoint so load balancers can detect cascading failures
- The timeout (how long to stay OPEN) needs tuning — too short means probing a still-broken service, too long means unnecessary downtime
- Consider using a library like opossum for production use — it handles edge cases you will miss
Revisions (0)
No revisions yet.