Skip to content

Resilience tuning

The narrative is in Resilience; this is the exhaustive surface — each strategy's constructor parameters and defaults, and the algorithm behind the adaptive controllers. A ResiliencePolicy composes these outer→inner: rate limit → bulkhead → circuit breaker → retry → timeout, plus optional fallback and hedge. The policy itself carries one more knob — fail_open_on_store_error (default True): when a shared breaker / rate-limit store errors, admit the call instead of failing it.

Rate limit

RateLimitStrategy — token bucket, sustained permits/per, capacity burst or permits. The bucket starts full and refills continuously; an empty bucket rejects immediately (no queuing) with exc.throttled(code="rate_limited"). State is keyed (policy, route).

Param Default Meaning
permits tokens issued per per window (>= 1)
per refill window
burst permits bucket capacity (max tokens saved up)

Retry & backoff

RetryStrategy re-runs on classified-retryable kinds only (concurrency, infrastructure, throttled); a non-retryable kind in retry_on fails at wiring.

Param Default Meaning
max_attempts total attempts including the first (>= 1)
backoff a BackoffStrategy (base, max, multiplier, jitter)
retry_on the ExceptionKinds that trigger a retry (all must be retryable)
budget None optional RetryBudget token-bucket cap on retries across calls

Timeout

TimeoutStrategy(timeout=…) — a per-attempt timeout; cancellation on expiry.

Circuit breaker

CircuitBreakerStrategy — rolling-window breaker; trips open when the failure fraction exceeds failure_ratio over sampling_window (once min_throughput calls have been seen), then probes half-open after break_duration.

Param Default Meaning
failure_ratio failure fraction in the window that trips open ((0, 1])
sampling_window rolling window outcomes are counted over
min_throughput minimum calls in the window before it may trip
break_duration how long it stays open before probing half-open
half_open_max_calls 1 probe calls allowed while half-open

Bulkheads

Cap concurrent in-flight calls. The three kinds are mutually exclusive within one policy; all share the queue-management controls below.

Fixed — BulkheadStrategy

Param Default Meaning
max_concurrency max concurrent in-flight calls
max_queue 0 calls allowed to wait for a slot before rejection

Adaptive (AIMD) — AdaptiveBulkheadStrategy

Latency sets the limit. Starts at max_concurrency; a completion over latency_threshold decreases it multiplicatively (*= backoff_ratio, at most once per cooldown), in-budget completions recover it additively (+= increase_step / limit — ~one slot per limit successes). This is AIMD (the TCP-congestion algorithm): N process-local limits sharing one downstream converge like N TCP flows sharing a link, so uncoordinated replicas need no shared state. The congestion signal is latency only — errors stay the breaker's job — and shrinking never evicts in-flight work, only gates admission.

Param Default Meaning
latency_threshold completion latency above this counts as congestion
max_concurrency ceiling and initial limit
min_concurrency 1 floor the limit never drops below
max_queue 0 calls allowed to wait for a slot
backoff_ratio 0.9 multiplicative decrease on a breach ((0, 1))
increase_step 1.0 additive recovery: increase_step / limit per in-budget completion
cooldown 1 s minimum spacing between decreases (coalesces a slow burst into one backoff)
latency_quantile None distributional signal — see below

latency_quantile (the p-quantile signal). By default any single completion over the threshold is a breach — instant, but one GC pause halves concurrency. Set latency_quantile=0.95 to breach only when the observed p95 of recent completions (a windowed streaming estimate — five floats, no sample storage) exceeds the threshold: outliers can't move a quantile, only a genuinely shifted distribution can. A backoff opens a fresh measurement epoch, so a stale-high quantile never ratchets the limit to the floor after recovery. In a fleet, the per-replica sketch can be shared via latency_digest_store (see Resilience → fleet-wide state).

Gradient — GradientBulkheadStrategy

Drops the threshold entirely. The Gradient2 controller (Netflix concurrency-limits) learns the no-load baseline RTT and tracks the gradient between it and recent latency, finding the load/latency knee on its own — no latency_threshold to tune. Probes up gently while latency sits near baseline, contracts as latency inflates. Only successful completions feed it.

Param Default Meaning
max_concurrency ceiling and initial limit
min_concurrency 1 floor
max_queue 0 calls allowed to wait for a slot
rtt_tolerance 1.5 latency-rise headroom before contracting (1.5 = tolerate a 50% rise)
smoothing 0.2 EWMA factor on limit increases (gentle up, fast down)
long_window 600 samples the no-load baseline RTT is averaged over
headroom 4.0 standing in-flight headroom probed toward while healthy

Queue management (every bulkhead kind)

Opt-in controls for a queued bulkhead (max_queue >= 1); the first two are from Facebook's Fail at Scale, the last is Netflix-style prioritized shedding.

Param Default Meaning
queue_target None CoDel target sojourn — under sustained congestion, a waiter parked longer than this is shed at dequeue (code="bulkhead_queue_shed"), bounding queueing by time, not length
queue_interval 100 ms CoDel congestion-detection window + the generous sojourn allowance while the queue has recently been empty (must exceed queue_target)
queue_adaptive_lifo False serve the newest waiter first while congested (its client is likeliest still listening); FIFO otherwise — pair with queue_target to shed the starved tail
prioritized False criticality-aware: a full queue admits a higher-criticality arrival by shedding the lowest, and lower tiers shed sooner. Tiers come from the task-scoped Criticality (BEST_EFFORT < DEGRADED < NORMAL < CRITICAL), bound with bind_criticality(...)

A parked waiter whose invocation deadline has expired is failed at wake rather than granted a slot it can't use — no knob needed.

Adaptive throttle

AdaptiveThrottleStrategy — probabilistic client-side shedding for a degraded downstream (Google SRE book). Where the breaker is binary, this sheds proportionally. Tracks requests and accepts per window and rejects locally with probability:

max(0, (requests − k·accepts) / (requests + 1))

A healthy downstream (accepts ≈ requests) computes a negative number — nothing is shed. As the accept ratio degrades, shedding rises proportionally; the steady state is self-limiting because shed calls count as requests but not accepts, so the client converges on ~k × the downstream's current capacity, leaving a continuous probe that detects recovery on its own (no half-open ceremony). Shed calls fail with a retryable throttled (code="adaptive_throttle"). Mutually exclusive with the circuit breaker in one policy. "Accepted" mirrors the breaker's classification inverted — a non-retryable (domain) failure is the downstream doing its job, so it never sheds.

Param Default Meaning
k 2.0 permissiveness multiplier (>= 1); higher tolerates more failure before shedding
window 2 min counting window; counters reset when it elapses, so shedding decays within one window of recovery
min_throughput 10 requests per window below which nothing is shed

Hedge

HedgeStrategy — race the tail: if the primary hasn't completed after delay, fire a concurrent copy and take the first to finish (losers cancelled). Only safe on idempotent reads; budget caps the extra load. Applied outside the strategy pipeline.

Param Default Meaning
delay wait before the next concurrent copy (~p95); the fallback until the estimator warms when adaptive_delay_quantile is set
max_attempts total concurrent attempts including the primary (>= 2)
budget None optional RetryBudget cap on extra attempts
adaptive_delay_quantile None hedge after the observed quantile of primary latencies (streaming P² per (policy, route)) instead of the fixed delayThe Tail at Scale; typical 0.95
delay_min None floor for the adaptive delay (guards over-eager hedging when every call is fast); requires adaptive_delay_quantile
delay_max None cap for the adaptive delay (guards a degraded downstream dragging the trigger past usefulness); requires adaptive_delay_quantile

The effective adaptive delay is the forze.resilience.hedge.delay gauge once instrument_resilience is attached.

Control plane

ResilienceAdminPort (ctx.resilience.admin(), ResilienceAdminDepKey) inspects and retunes live policy state without a redeploy:

Method Notes
inspect(*, policy=None) ResilienceStateSnapshots — per (policy, route): forced_open, the adaptive concurrency_limit, in_use, waiting, and the effective hedge_delay
force_open(policy, route=None) manual kill-switch — trip the breaker open (route=None = every route)
clear_forced_open(policy, route=None) lift a manual trip
retune(policy) hot-swap a ResiliencePolicy by name; drops that policy's cached adaptive state