Circuit breaker — the state machine

A circuit breaker is a three-state machine — closed, open, half-open — that fast-fails requests to a broken dependency and periodically probes it for recovery, so the caller stops waiting on timeouts it can already predict will fail.

Previously

A retry budget caps how much extra load the fleet generates, but each individual caller is still spending its own time and connections waiting for a backend it could already tell is broken. What's missing is a switch that says 'stop calling this dependency for a while' — and that switch is the circuit breaker.

Scene 07

Circuit breaker — the state machine

  1. Watch
  2. Try it
  3. Predict
  4. Capture
CIRCUIT BREAKER · STATE MACHINEsustained errors > thresholderr 0% / 50%cooldown elapsedprobe succeededprobe failedCLOSEDtraffic flowsOPENfast-failHALF-OPENone probeREQUEST STREAMCLOSEDbackendreachable42ms42ms42ms42ms42ms42ms42ms42msSTOPWATCH~42ms · normalCALLER EXHAUSTION10%caller pool healthyHealthy backend. Breaker CLOSED — every request reaches the backend at normal latency.
closed: requests flow through →
What to watch for

Watch the breaker trip. Errors climb past the threshold; CLOSED hands off to OPEN; new requests fast-fail in milliseconds. After a cooldown, HALF-OPEN admits exactly one probe — and the result decides whether the breaker closes or stays open.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Breaker.onRequest
the three-state machine: closed, open, half-open
state = CLOSED
error_rate_window = sliding(60s)
open_started_at = 0
def on_request(req):
if state == CLOSED:
outcome = forward(req, timeout=T)
error_rate_window.record(outcome)
if error_rate_window.error_rate() > THRESHOLD:
state = OPEN
open_started_at = now()
return outcome
if state == OPEN:
if now() - open_started_at > COOLDOWN:
state = HALF_OPEN
else:
return fast_fail_503() # ~1ms, no backend call
if state == HALF_OPEN:
outcome = forward(req, timeout=T) # single PROBE
state = CLOSED if outcome.ok else OPEN
open_started_at = now()
return outcome
Why OPEN protects the caller
the asymmetric payoff: 1ms reject vs full-timeout wait
# Without breaker: every caller waits the full timeout.
# N callers * T seconds = N*T thread-seconds blocked.
# Caller's thread pool fills with stuck requests.
# With breaker OPEN: every caller returns in ~1ms.
# Caller frees the resource and degrades gracefully.
# Traffic to OTHER (healthy) dependencies keeps flowing.
Envoy.cluster.circuit_breakers (footnote)
connection-pool ceilings — NOT the state machine above
# Envoy's `circuit_breakers` config is connection-pool
# ceilings per cluster, not closed/open/half-open.
# Per-replica state-machine semantics live in
# outlier_detection (next scene).
cluster:
circuit_breakers:
max_connections: 1024
max_pending_requests: 1024
max_requests: 1024
max_retries: 3

Where this sits in Build a Service Mesh (Envoy / Istio style)

Scene 07 of 13. Closed → open → half-open. Fast-fail to a known-broken dependency and periodically probe for recovery, so callers stop wasting resources on guaranteed failures.

Up next. A breaker per cluster decides 'is the dependency broken' as a whole — but a cluster usually has many replicas, and the bad apple is just one of them. That finer-grained decision is the next scene.

All 13 scenes in Build a Service Mesh (Envoy / Istio style) · Every curriculum

Built with Arqly
Every scene in Build a Service Mesh (Envoy / Istio style) builds on the one before it.All 13 Build a Service Mesh (Envoy / Istio style) scenes