MSc thesis
Tolerant
What a circuit breaker actually costs you
- design
- 2 × 5 factorial
- conditions
- 10
- runs
- 150 over ~72 h
- stack
- Python, FastAPI, Docker Compose, Locust, Scaphandre, Prometheus
The question
Circuit breakers are standard advice. Wrap the call, trip the breaker when the downstream service starts failing, stop hammering something that is already down. Everyone repeats it. Almost nobody publishes what it costs.
I screened 44 papers on fault tolerance in microservices.2 measured energy at all, and neither measured it as a consequence of a pattern firing. That is the gap I built this for.
What I built
A ten service testbed, a reimplementation of Google's Online Boutique in Python and FastAPI on Docker Compose. 7 services sit behind an application level circuit breaker I wrote myself rather than pulled in, so every state transition is mine to log. It opens after 5 consecutive failures, waits 30 seconds, then lets one request through and needs 2 clean successes before it closes.
Failures go in at Payment as instant HTTP 500s at a rate I set per run. Checkout depends on Payment, so a Payment failure is a real dependency failure travelling through the graph, not a synthetic one at the edge.
How I measured
Two factors. Breaker off or on, and Payment failing at0, 25, 50, 75, 100 percent. 10 conditions. Locust drives 100 concurrent users for 30 minutes per condition. Energy comes off the CPU's RAPL counters, read by Scaphandre and scraped into Prometheus every 2 seconds. The first 300 seconds of every run get cut so warmup never lands in a number.
Then I ran all 10 conditions 15 times.150 experiments, roughly 72 hours. Every figure below has a confidence interval behind it rather than one lucky run.

What I found
It is a threshold, not a dial
| Payment failing | Opens | Chance any 5 in a row all fail |
|---|---|---|
| 0% | 0 | 0 |
| 25% | 2 | 0.10% |
| 50% | 37 | 3% |
| 75% | 54 | 24% |
| 100% | 57 | 100% |
At 25 percent the breaker barely notices. It needs 5failures in a row, and at that rate a five request window comes up all bad about once in a thousand tries. A quarter of your checkouts are dying and the breaker sits there closed, doing nothing. That is not a misconfiguration. It is what a consecutive failure counter means, and most people ship one without working out where its blind spot sits.
The top end is arithmetic, not behaviour. One open costs30 seconds of cooldown plus about a second to trip again, so1800 seconds of load divided by 31.3 seconds a cycle caps you at 57.5 opens. I measured 57..
At total outage it earns its keep
Checkout p95 drops from 530 ms to130 ms, about 4 times faster. That is the floor, not the ceiling. My injected failures return instantly, so there are no timeout waits for the breaker to escape. Against a dependency that hangs instead of erroring, the gap is wider.
At partial failure you pay for it
At 50 percent injected, observed checkout failure was79 percent. The extra29 points are the breaker turning away requests that would have gone through. At 75 percent it costs1.5 points of availability and12 percent of throughput. Roughly half of the 896 rejected requests would have succeeded.
Failure spread through the cart, not the thread pool
This one I did not plan for. The standard cascade story is blocked threads and exhausted pools. That is not what happened. Carts empty only after a payment succeeds, so every suppressed checkout leaves its cart intact, and the cart view fans out one catalog call per item. Over 30 minutes the carts grow and the fan out grows with them. /cart went from295 ms to 2,305 ms inside a single run. The failure propagated through persistent application state. No thread ever blocked.
Same watts, more joules per useful transaction
| Payment failing | Change |
|---|---|
| 0% | ~0 |
| 25% | ~0 |
| 50% | +7.6% |
| 75% | +10.6% |
| 100% | ~0 |
Raw power does not move. Across 15 rounds every delta sits inside1.3 percent and every confidence interval crosses zero. What moves is energy per successful request. The machine draws the same power and completes less work with it, so each transaction that does land costs more.
What it adds up to
The breaker relocates cost. It takes it off latency and energy when the dependency is fully down, and puts it onto availability and throughput when the dependency is only half down. Good trade at total outage, a real bill at partial failure, and which one you are in decides whether you want it.
One number changed on the way here. A single run had suggested the breaker drew up to 11 percent less power. 15 rounds killed it. The per transaction penalty survived, and turned out to be the sharper result anyway.