Counting failures in a PHP object means each of fifty workers keeps its own count and opens independently, which is fifty times more slow calls than intended.
private function recordFailure(): void
{
$key = "cb:{$this->service}:failures";
$count = $this->redis->incr($key);
if ($count === 1) {
$this->redis->expire($key, $this->windowSeconds);
}
if ($count >= $this->threshold) {
$this->redis->setex("cb:{$this->service}:open", $this->cooldown, 1);
}
}
Using an expiring key as the open state removes the need for a timer: the circuit closes when the key expires and half-open is simply the first call after it has. The breaker now depends on Redis, so the state check needs a short timeout and a fail-open default — if the bookkeeping store is unreachable, pass the call through. A breaker that blocks traffic because its own dependency is down has become the outage.