Adaptive concurrency limits the number of in-flight requests at any moment by inferring a safe ceiling from observed latency, rather than from a statically configured number. When the system is fast and under-utilized, the ceiling grows. When latency climbs (indicating queueing behind the scenes) or requests start timing out, the ceiling shrinks. This is the right primitive when you don’t know the right rate limit in advance, or when capacity changes dynamically. The implementation is modeled on Netflix’s concurrency-limits library and supports two inference laws: the default gradient2 algorithm (RTT-gradient based, continuous update) and AIMD (additive-increase/multiplicative-decrease, similar to TCP congestion control).Documentation Index
Fetch the complete documentation index at: https://mintlify.com/AmeyaBorkar/throttlekit/llms.txt
Use this file to discover all available pages before exploring further.
How it works
Each request acquires aLease from the ConcurrencyGuard. If the number of in-flight requests is below the current inferred ceiling, the lease is granted (ok: true) and the caller proceeds. If the ceiling is reached, the lease is rejected (ok: false) — the caller should shed the request (e.g., respond 503) rather than queue it.
When the caller finishes the request, it calls lease.release(). The guard records the request’s latency (time between acquire() and release()), feeds it into the inference law, and updates the ceiling.
Gradient2 (default)
Compares the best-observed (“no-load”) RTT to the current RTT. While the ratio stays near 1, the limit grows by√limit of headroom. As the current RTT climbs above the no-load baseline, a gradient drives the limit down multiplicatively. An EMA (smoothing) prevents oscillation:
rttWindow samples, so it can rise again after a deploy or load shift — “best recently observed”, not an all-time minimum.
AIMD
On a healthy request (RTT ≤tolerance * noload): additive increase (+1), but only while actually pushing the ceiling. On a dropped request or RTT overshoot: multiplicative decrease (estimate × backoffRatio). Simpler and more aggressive than gradient2.
Options
Hard floor on the inferred ceiling. The estimate will never drop below this. Default
4.Hard ceiling on the inferred ceiling. The estimate will never exceed this. Default
512.Where the estimate starts. Default equals
minLimit. The guard begins conservative and grows toward capacity as it observes healthy latency.Which inference law drives the limit. Default
"gradient2". Use "aimd" for simpler, more aggressive decrease behavior.Sample count for the rolling-minimum no-load RTT baseline. Default
100. Larger values make the baseline more stable but slower to adapt after a load shift.Gradient2 EMA factor in
(0, 1]. Larger values react faster to RTT changes; smaller values are more stable. Default 0.2.Headroom factor applied to the no-load RTT. A gradient of
tolerance * rttNoload / rtt above 1.0 is clamped to 1.0; below 0.5 is clamped to 0.5. Default 2.0 — the limit starts decreasing when RTT exceeds twice the no-load baseline.AIMD multiplicative decrease factor, in
[0.5, 1). Default 0.9. Only used when algorithm: "aimd".Opt-in Envoy-style forced minRTT recalibration. When set, the guard periodically drains to
probeLimit concurrency and measures the true no-load RTT, then adopts it as the fresh baseline. Off by default.intervalMs— re-probe at most this often (ms). Default60_000.probeLimit— effective ceiling during a probe. DefaultminLimit.probeSamples— clean low-concurrency samples to collect before adopting the baseline. Default5.
Injectable time source. Default system clock.
ConcurrencyGuard interface
Lease interface
ok—falsemeans the request is over the inferred ceiling and should be shed.release({ dropped: true })— signals that the request failed or timed out, which is treated as an overload signal and triggers a larger decrease than a slow but successful request.release()is idempotent and safe to call detached:const r = lease.release; r().
Code example
Full example with burst of concurrent work
Distributed adaptive concurrency
For fleet deployments,distributedAdaptiveConcurrency coordinates the inferred ceiling across multiple processes. Each node runs a private adaptiveConcurrency guard for RTT inference, and a ConcurrencyCoordinator aggregates all nodes’ local limits (L_local) into one global ceiling (L_global) and distributes equal shares to each node.
The effective per-node ceiling is min(share, local.limit) — whenever the outer gate admits, the local guard is guaranteed to return ok: true.
DistributedAdaptiveConcurrencyOptions (key fields)
| Option | Type | Default | Description |
|---|---|---|---|
coordinator | ConcurrencyCoordinator | required | Cross-node coordinator that owns L_global |
nodeId | string | required | Unique per-process identity |
key | string | "" | Shared-backend key (all nodes fronting the same backend must match) |
local | AdaptiveConcurrencyOptions | {} | Forwarded to the private adaptiveConcurrency |
heartbeatMs | number | 1000 | Heartbeat / lease-renewal period in ms |
leaseTtlMs | number | 2 * heartbeatMs | Lease TTL; must exceed heartbeatMs |
onCoordinatorOutage | "fail-closed" | "local-only" | "fail-closed" | Behavior when the coordinator is unreachable |
When to use adaptive concurrency
- Unknown or changing capacity — when you don’t know the right static concurrency limit, or when it shifts with deployments and load patterns.
- Latency-sensitive services — when you want to automatically shed load as soon as latency climbs, before queues overflow.
- Protection against overload cascades — when a downstream dependency slows down, the guard automatically reduces admission to the affected service.
- Multi-service fan-out — use
distributedAdaptiveConcurrencywhen N independent processes front one shared backend and you need the fleet’s total in-flight count to stay under one cooperatively-inferred global ceiling.