A fixed concurrency limit needs a number, and every number is wrong somewhere: too low wastes capacity on a good day, too high lets requests pile up in the dependency’s queue on a bad one. The adaptive limiter finds the number by watching the dependency. While latency stays flat, it raises the limit. When latency climbs or calls time out, the dependency is queueing, and the limit drops. This is how TCP congestion control (and Netflix’s concurrency-limits) works. Use it in front of a dependency whose capacity changes (autoscaling, shared databases, noisy neighbours). Don’t use it when you need a hard, contractual cap, such as a connection pool size: use a fixed limiter for that.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Concurrency limit that tunes itself from observed latency and failures (gradient2 / vegas / aimd),
so you don't have to guess the right number: it grows while the dependency keeps up and backs off when it queues.
addAdaptiveConcurrencyLimiter({
AdaptiveConcurrencyLimiterOptions<any>.minLimit?: number |undefined
Default 1.
minLimit: 5,
AdaptiveConcurrencyLimiterOptions<any>.maxLimit?: number |undefined
ResiliencePipelineBuilder adds Polly-style addRetry() / addTimeout() / … shortcuts on top, at the cost of
bundling every strategy. Strategies run in the order they are added: the first one added is the outermost.
Options are validated and strategy state (e.g. circuit breaker) is created on build().
ResiliencePipelineBuilder adds Polly-style addRetry() / addTimeout() / … shortcuts on top, at the cost of
bundling every strategy. Strategies run in the order they are added: the first one added is the outermost.
Options are validated and strategy state (e.g. circuit breaker) is created on build().
gradient2: limit 53 before, 19 during and 59 after the slowdown; 5 drops.
Limit, 5–10 s53
Limit, 15–20 s19
Limit, 25–30 s59
Drops counted5
Simulated dependency: 40 ms per call while up to 30 run at once (10 while slowed); above that calls queue inside it and latency grows. Offered load is 1500 req/s, so the limit, not the traffic, decides how many run. Each new limit comes from the library's gradient2(), with the strategy's clamping, app-limited and one-cut-per-round-trip rules.
Admission is the same as the fixed limiter: run if inflight < limit, else wait in a FIFO queue (up to queueLimit), else reject with BulkheadRejectedError. The difference is what happens after each call:
The strategy measures the call’s latency (rtt) and whether it was a drop, an outcome that signals overload (shouldDrop; by default timeouts, rejections and transient errors).
It feeds that sample to the algorithm, which returns a new limit.
The new limit is clamped to [minLimit, maxLimit]. If its whole-number value changed, OnLimitChanged fires and queued waiters are let in.
Three guards keep the limit honest:
Caller aborts are ignored. A client that hung up says nothing about the dependency. A timeout abort does count (the call was too slow).
One cut per round trip. Calls admitted before the last cut ran under the old, higher limit. Their drops are expected, so they don’t cut the limit again (as in TCP).
No growth while app-limited. If less than half the limit was in use, low latency proves nothing about higher concurrency, so the limit doesn’t grow.
The limit is fractional internally; limit reports whole permits.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
TCP Vegas applied to concurrency. The fastest latency seen is the no-queue latency; with limit calls in flight at latency rtt,
limit * (1 - minRtt / rtt) of them are waiting in the dependency's queue. Keep that queue between alpha and beta: below means
unused capacity, above only adds latency. Thresholds scale with log10(limit) so large limits tolerate a longer queue.
Instead of Vegas' fixed +-1 steps, the limit moves toward the band by the queue error (damped), which converges in a few round trips.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Concurrency limit that tunes itself from observed latency and failures (gradient2 / vegas / aimd),
so you don't have to guess the right number: it grows while the dependency keeps up and backs off when it queues.
TCP Vegas applied to concurrency. The fastest latency seen is the no-queue latency; with limit calls in flight at latency rtt,
limit * (1 - minRtt / rtt) of them are waiting in the dependency's queue. Keep that queue between alpha and beta: below means
unused capacity, above only adds latency. Thresholds scale with log10(limit) so large limits tolerate a longer queue.
Instead of Vegas' fixed +-1 steps, the limit moves toward the band by the queue error (damped), which converges in a few round trips.
vegas({
VegasOptions.alpha?: number |undefined
Grow while the estimated queue is below alpha * log10(limit). Default 3.
alpha: 2,
VegasOptions.beta?: number |undefined
Shrink when the estimated queue is above beta * log10(limit). Default 6.
beta: 4 }),
AdaptiveConcurrencyLimiterOptions<any>.initialLimit?: number |undefined
gradient = tolerance × baseline / shortLatency, clamped to [0.5, 1]. At 1 (latency within tolerance), the limit grows by queueSize(limit); below 1, it shrinks in proportion. The new estimate is blended into the old one by smoothing. The baseline drops at once to any lower short-term latency, and rises slowly (over longWindow samples) only while latency is within tolerance. A plain long-term average would instead drift up to the overloaded latency under sustained overload, and the limit would creep up without bound. Samples with rtt of 0 (a coarse clock such as Date.now()) carry no latency signal and are ignored.
Option
Type
Default
Description
tolerance
number
1.5
How far short-term latency may exceed the baseline before the limit shrinks. ≥ 1.
shortWindow
number
10
Samples in the short-term latency EMA.
longWindow
number
600
Samples in the long-term baseline EMA.
smoothing
number
0.2
Weight of each new estimate, in (0, 1]. Lower is steadier.
queueSize
(limit) => number
Math.sqrt
Headroom added on top of limit × gradient, so the limit can grow.
TCP Vegas applied to concurrency. The fastest latency seen is the no-queue latency, so with limit calls in flight at latency rtt, about limit × (1 − minRtt / rtt) of them are queued inside the dependency. The limit moves to keep that queue between alpha × log10(limit) and beta × log10(limit): below means unused capacity, above only adds latency. A drop cuts the limit by beta × log10(limit).
Option
Type
Default
Description
alpha
number
3
Grow while the estimated queue is below alpha × log10(limit). > 0.
beta
number
6
Shrink when the estimated queue is above beta × log10(limit). > alpha.
Any object with onSample(sample) => number works. The sample is { rtt, inflight, dropped, limit }; return the next limit (the strategy clamps it, and ignores non-finite values).
import { type
(alias) interfaceLimitAlgorithm
importLimitAlgorithm
Computes the next concurrency limit from a sample. Stateful: give each strategy its own instance. The strategy clamps the result to [minLimit, maxLimit].
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
// Halve on overload, otherwise creep up by one per call.
const
consthalving:LimitAlgorithm
halving:
(alias) interfaceLimitAlgorithm
importLimitAlgorithm
Computes the next concurrency limit from a sample. Stateful: give each strategy its own instance. The strategy clamps the result to [minLimit, maxLimit].
LimitAlgorithm= {
LimitAlgorithm.onSample(sample: LimitSample): number
onSample: ({
dropped: boolean
The dependency showed overload (timeout, rejection, transient error).
dropped,
limit: number
The current limit (may be fractional).
limit }) => (
dropped: boolean
The dependency showed overload (timeout, rejection, transient error).
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Concurrency limit that tunes itself from observed latency and failures (gradient2 / vegas / aimd),
so you don't have to guess the right number: it grows while the dependency keeps up and backs off when it queues.
AdaptiveLimitProvider reads the live state of one limiter, e.g. for a health endpoint or a gauge. A provider attaches to a single limiter; attaching it twice throws. The strategy instance (pipeline.getStrategy('AdaptiveConcurrencyLimiter')) has the same limit, inflight and queued getters.
import {
classAdaptiveLimitProvider
Reads the live limit of one adaptive limiter (health checks, dashboards).
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
Concurrency limit that tunes itself from observed latency and failures (gradient2 / vegas / aimd),
so you don't have to guess the right number: it grows while the dependency keeps up and backs off when it queues.
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
Schedules repeated execution of callback every delay milliseconds.
When delay is larger than 2147483647 or less than 1 or NaN, the delay
will be set to 1. Non-integer delays are truncated to an integer.
If callback is not a function, a TypeError will be thrown.
This method has a custom variant for promises that is available using
timersPromises.setInterval().
@since ― v0.0.1
@param ― callback The function to call when the timer elapses.
@param ― delay The number of milliseconds to wait before calling the
callback. Default:1.
@param ― args Optional arguments to pass when the callback is called.
@returns ― for use with clearInterval()
setInterval(() =>
var console:Console
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
The whole-number limit changed. Args: { oldLimit, newLimit, sample }.
OnRateLimiterRejected
onRejected
An execution was rejected.
Rejections are BulkheadRejectedError (code: 'BULKHEAD_FULL', HTTP 503) with reason'full' or 'queue-timeout', and the active / queued counts. See the concurrency limiter.
Set maxLimit to what you can afford. The limiter protects the dependency, not your process. Keep maxLimit at or below your connection pool size.
Custom drops. If the dependency signals overload in a response (HTTP 429 or 503 returned as a value), say so with shouldDrop: ({ outcome }) => !outcome.ok || outcome.result.status === 503.
Slower forever. Both latency algorithms remember the fastest latency. If the dependency gets permanently slower (2× at low load), the limit settles lower. Restart or recreate the pipeline after such a change.