Circuit Breakers

Automatically detect failing backends and stop sending traffic to them. Circuit breakers prevent cascade failures and give your upstream services time to recover.

How It Works

Nolxy implements a per-route circuit breaker with three states:

CLOSED

Normal operation. All requests pass through. Failures are counted.

OPEN

Circuit tripped. All requests immediately return 503. Backend gets time to recover.

HALF-OPEN

Probe phase. One request is allowed through. Success → CLOSED. Failure → OPEN.

Configuration

Circuit breakers are configured per-route in the dashboard or via Gateway as Code:

Failure Threshold
Number of failures before the circuit opens (default: 5)
Monitoring Window
Time window for counting failures in milliseconds (default: 60,000ms)
Recovery Timeout
How long the circuit stays open before transitioning to half-open (default: 30,000ms)
Success Threshold
Successful requests needed in half-open to close the circuit (default: 2)

Implementation Details

  • Atomic state management — Circuit state transitions use Redis Lua scripts to prevent race conditions across multiple gateway workers.
  • In-memory caching — An LRU cache stores circuit state locally to reduce Redis calls on the hot path. Cache entries have short TTLs to stay fresh.
  • Notifications — When a circuit opens or closes, Nolxy sends notifications via email and webhooks (if configured).
  • Retry-After header — When a circuit is open, the 503 response includes a Retry-After header indicating when the client should retry.

Lua Script (Simplified)

The CAN_REQUEST script runs atomically on Redis:

-- State check
local state = redis.call('GET', KEYS[1]) or 'CLOSED'

if state == 'CLOSED' then
    return {1, 'CLOSED', 0}  -- allowed
end

if state == 'HALF_OPEN' then
    return {1, 'HALF_OPEN', 0}  -- probe allowed
end

-- OPEN: check if timeout elapsed
local openedAt = tonumber(redis.call('GET', KEYS[2]))
local elapsed = tonumber(ARGV[2]) - openedAt
local timeout = tonumber(ARGV[1])

if elapsed >= timeout then
    redis.call('SET', KEYS[1], 'HALF_OPEN')
    return {1, 'HALF_OPEN', 0}  -- transition to probe
end

local retryAfter = math.ceil((timeout - elapsed) / 1000)
return {0, 'OPEN', retryAfter}  -- blocked

Plan Requirements

Circuit Breakers are available on Pro plans and above.