Lesson 7 / 25
How a Circuit Breaker Works
Describe the closed, open and half-open states and the thresholds that move between them.
Stop calling what is broken
A circuit breaker, popularised by Michael Nygard's book Release It!, wraps calls to a dependency and tracks their outcomes. In the closed state calls pass through normally while the breaker records failures (errors, timeouts and optionally slow calls) in a sliding window (the last N calls or the last N seconds). When the failure rate crosses a threshold (for example 50% of at least 20 calls), the breaker opens: calls fail immediately with an error or fallback, without touching the dependency. This protects the caller's resources and gives the dependency room to recover. After a wait duration (say 30 seconds), the breaker moves to half-open and lets a small number of trial calls through. If they succeed, it closes; if they fail, it opens again for another wait period. Breakers turn a slow, resource-draining failure into a fast, cheap one.
Closed, open, half-open
Failures open the breaker; after a wait, trial calls decide whether it closes again.
A minimal circuit breaker
Count-based window, failure-rate threshold and a half-open trial.
import time
from collections import deque
class CircuitOpen(Exception): pass
class CircuitBreaker:
def __init__(self, window=20, min_calls=10, failure_rate=0.5, open_seconds=30):
self.results = deque(maxlen=window)
self.min_calls, self.threshold, self.open_seconds = min_calls, failure_rate, open_seconds
self.state, self.opened_at = "closed", 0.0
def call(self, fn):
if self.state == "open":
if time.monotonic() - self.opened_at < self.open_seconds:
raise CircuitOpen() # fail fast
self.state = "half_open" # allow a trial call
try:
result = fn()
except Exception:
self._record(False)
raise
self._record(True)
return result
def _record(self, ok):
if self.state == "half_open":
self.state = "closed" if ok else "open"
self.opened_at = time.monotonic()
self.results.clear()
return
self.results.append(ok)
failures = self.results.count(False)
if len(self.results) >= self.min_calls and failures / len(self.results) >= self.threshold:
self.state, self.opened_at = "open", time.monotonic()Require a minimum number of calls
Without a minimum, two failures out of the first three calls after a deploy would open the breaker. A minimum call count prevents tripping on tiny samples.
Quick check: What happens to calls while a circuit breaker is open?
- They fail fast (or use a fallback) without calling the dependency
- They are queued until the dependency recovers
- They are retried immediately
- They are sent to a random service
Answer
They fail fast (or use a fallback) without calling the dependency — An open breaker short-circuits calls to protect both caller and dependency.