Circuit breaker
Three trip conditions
1. failure rate > threshold, over a dual-bound window (n >= minCalls)
2. SLOW-CALL rate > threshold, over the same window (n >= minCalls)
3. consecutive failures >= backstop (window-independent)The window is dual-bound — the last calls samples and only those within maxAgeMs. Count-only windows go stale at low traffic (at 8 req/min, "the last 100 calls" spans ~12 minutes). Time-only windows are unbounded at high traffic.
Condition 3 closes a hole every rate-based breaker has. Rate conditions cannot fire below minCalls, so a completely dead upstream at low traffic never accrues enough samples and therefore never trips — every caller eats the full timeout.
Half-open admits exactly one probe by default, so recovery cannot stampede an upstream that is by definition fragile. It self-heals if a probe is admitted and never settles.
When a circuit breaker is the wrong tool
Worth saying plainly, because it is the best-known criticism of the pattern and it is correct. Marc Brooker's argument, in short: circuit breakers turn partial failures into complete ones. If one shard of a sharded backend is overloaded while the rest are healthy, a breaker either trips — degrading every caller hitting the healthy shards — or it does not trip, in which case it is doing nothing. His example is heterogeneous load: one key range gets hammered, the others idle, and the client cannot tell from outside whether the backend is down or merely hot for particular parameters.
Three practical consequences:
- Key by the thing that fails independently, not by host.
key: (req) => req.shardIdorkey: (req) => req.tenantis usually more correct than keying by hostname. resilix keys by whatever you return, so this is your choice to get right. - If the failure domain is not visible in the request, do not use a breaker. No key choice helps when you cannot see which shard you are talking to. Shed load proportionally instead — that is what the adaptive limiter is for (v0.3), and a partial outage then produces partial shedding rather than an all-or-nothing decision.
- A breaker is right for a homogeneous upstream that is wholly up or wholly down. That is the case resilix was built for: one provider, one endpoint, degrading as a unit.