Protect the caller when a dependency fails
A circuit breaker stops a caller from repeatedly attempting operations that are failing. It does not repair the dependency or guarantee that a fallback answer is valid. Use it with request deadlines, bounded concurrency and an explicit error contract.
The Azure pattern reference distinguishes a breaker from retry logic and describes the closed, open and half-open states.
Three consecutive failures open the circuit. After three seconds, one trial request is allowed; success closes it and failure reopens it.
Worked scenario: search suggestions
Assume a page can load without suggestions. A hypothetical suggestions service receives 100 calls/s; the caller allows ten concurrent requests and a 250 ms deadline. If every call times out, those ten slots can still be exhausted before a useful response returns. Retrying all failures immediately adds work to a service that is already struggling.
A breaker can stop new attempts after an observed failure threshold, allowing the main page to return a clearly unavailable suggestions section. An empty success response would hide the failure and pollute metrics. For a payment authorization dependency, the fallback must instead refuse or defer the action; inventing a successful authorization would break correctness.
Define each state precisely
| State | Admission rule | Evidence needed to move |
|---|---|---|
| Closed | Admit within normal concurrency limits | A threshold of eligible failures after enough samples |
| Open | Reject locally without contacting the dependency | A configured cooldown expires |
| Half-open | Allow a bounded number of probes | Successful probes close it; a probe failure reopens it |
A single successful request is weak evidence during a partial outage. A production library may support a rolling failure ratio, minimum call volume, slow-call thresholds and several probes. Choose these according to traffic and error semantics rather than copying one threshold into every service.
A small deterministic state model
This Python example models sequential admission and completion. It is deliberately not thread-safe, permits only one outstanding call, and does not wrap networking. Use a monotonic clock value as now; the caller must invoke complete once for an admitted request. A concurrent breaker needs synchronization and protection against late completions from an earlier state generation.
class Breaker:
def __init__(self, threshold=3, cooldown=5):
if threshold < 1 or cooldown <= 0:
raise ValueError("positive threshold and cooldown required")
self.threshold, self.cooldown = threshold, cooldown
self.state, self.failures = "closed", 0
self.reopen_at = 0
self.in_flight = False
def admit(self, now):
if self.in_flight:
return False
if self.state == "open":
if now < self.reopen_at:
return False
self.state = "half-open"
self.in_flight = True
return True
def complete(self, succeeded, now):
if not self.in_flight:
raise RuntimeError("no admitted request")
self.in_flight = False
if succeeded:
self.state, self.failures = "closed", 0
else:
self.failures += 1
if self.state == "half-open" or self.failures >= self.threshold:
self.state = "open"
self.reopen_at = now + self.cooldown
breaker = Breaker()
for now in (0, 1, 2):
assert breaker.admit(now)
breaker.complete(False, now)
assert not breaker.admit(3)
assert breaker.admit(7) # One recovery probe
assert not breaker.admit(7)
breaker.complete(True, 7)
assert breaker.state == "closed"
Coordinate retries and recovery
Keep retries inside one end-to-end time budget. Use jitter to avoid synchronized retry waves, and retry state-changing operations only with appropriate idempotency and outcome recovery. A breaker for each process may produce many simultaneous half-open probes across a fleet; local bounds do not automatically create a global bound.
Scope breakers by dependency and failure domain. One failing tenant or optional endpoint should not necessarily block all healthy operations. Count dependency failures separately from validation errors and caller cancellations.
Test the unhappy path
Force timeouts until the breaker opens. Verify that no new downstream requests start during cooldown, and that rejected calls have a distinct metric. Delay a successful call until after another call opens the breaker; it must not incorrectly close a newer failure generation. Fail a probe and confirm the circuit reopens. Finally restore the dependency and measure recovery without releasing the entire queued backlog at once.
Continue with backpressure, rate limiting and observability.
Keep reading
Notification Delivery: Queues, Retries and Deadlines
Design a notification service around provider acceptance, recipient delivery, retry ambiguity, queue age and changing preferences.
API Gateway Pattern
The single entry point for microservices. Implementing rate limiting, authentication, and protocol translation/aggregation.
Caching Strategies
A breakdown of where to place your cache and how to keep it in sync with your database.
Microservices Architecture
An architectural style that structures an application as a collection of loosely coupled, independently deployable services.
Payment Workflows: Idempotency, Ledgers and Reconciliation
Trace a payment through an ambiguous timeout, concurrent retries, balanced ledger entries and evidence-based reconciliation.
Rate Limiting
Traffic control mechanism that limits the number of requests a user can make to prevent abuse, ensure fairness, and protect system resources from overload.