xmuu.my.id
← All posts

Circuit breaker — when to stop trying

3 min readbackendpatternsystem-design

Every call to another service can fail — network drops, the other service is overloaded, timeouts. Our first reflex is usually retry. But retrying while the target is dying adds load: failed requests get re-attempted, queues pile up, and the failure spreads to the callers. That's a cascade failure.

A circuit breaker solves it with simple logic: if the destination is proven sick, stop trying. Fail fast, wait, probe carefully, then return to normal.

The three breaker states

error rate ≥ thresholdcooldown elapsedprobes succeed ×2probe failsCLOSEDtraffic flowsOPENfail fastHALF-OPENlimited probes
Circuit breaker state machine — hover to highlight, click a node for details
  • CLOSED — everything normal, requests flow. What's watched: the error rate over a window (say, the last 10 requests).
  • OPEN — threshold exceeded. All requests are rejected instantly without touching the sick service. This is what saves the system: no resources burned waiting for timeouts.
  • HALF-OPEN — after a cooldown, the breaker allows a few probes. Consecutive successes → back to CLOSED; a failure → OPEN again.

Play with it yourself

The simulation below runs a service with a failure rate you control. Push the rate above the 60% threshold and watch the sequence: the window fills with failures → the breaker trips (OPEN) → subsequent requests are rejected instantly (hollow circles) → cooldown → HALF-OPEN → recovery, or another trip.

INTERACTIVE SIMULATION

CLOSED

traffic flows

error ≥ threshold →

OPEN

fail fast

cooldown 3 ticks →

HALF-OPEN

probe

← probe ok → CLOSED

requests →…

trip → rejected instantly

window 10 · failed 0 · rejected 0 · total 0

Without vs with a breaker

Every request waits out its timeout (say 30 seconds). With 100 requests/sec while the service is down, some 3,000 requests are hanging at any moment — the caller's thread pool exhausts, and healthy services topple with it.

One request, the full story

  1. A client calls service B through a breaker that is currently CLOSED.

Implementation notes

A few things decide whether a breaker is pleasant or painful in production: pick the threshold and window based on the caller's capacity (not library defaults); use different cooldowns for client errors vs infrastructure errors; make sure breaker state is exposed to monitoring — a silently OPEN breaker is an incident waiting to happen; and provide a meaningful fallback for every caller, because failing fast without a fallback just relocates the failure.

The pattern ships in nearly every stack: resilience4j (JVM), pybreaker/circuitbreaker (Python), Polly (.NET), opossum (Node). The library matters less than the threshold, cooldown, and fallback decisions that fit your system.