In-memory state is per process. With ten instances behind a load balancer you get ten caches, ten “global” rate limits and ten circuit breakers that each have to learn on their own that a dependency is down. resilience-pipeline/redis moves that state to Redis:
The module imports no Redis library. It needs one method, ioredis’ eval(script, numKeys, ...keysAndArgs), typed as RedisClient. Every operation is a single Lua script, so each one is atomic on the server.
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().
Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.
signal), {
ExecuteOptions.operationKey?: string |undefined
operationKey: 'product:42' });
Entries are stored as JSON with SET … PX, so they expire natively after ttl + staleTtl. Values must be JSON-serializable: a Date comes back as a string, a Map as {}. Concurrent misses are coalesced per process, not across instances.
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().
Fencing token of the innermost lock the calling code runs under. Pass it to the protected resource so it can reject stale writers.
getLockToken } from'resilience-pipeline';
import {
classRedisLockStore
LockStore on a single Redis primary (no Redlock): lease = SET NX PX, fencing token = INCR of a per-key counter.
The counter never expires (it must only grow), so one small key per lock key is kept forever.
Caveats: Redis replication is async, so after a failover a lease (or counter increments) can be lost: two holders,
or a repeated fencing token. For tokens to survive restarts, run with AOF (appendfsync everysec or always).
The protected resource should still reject writes whose token is below the highest it has seen.
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().
LockStore on a single Redis primary (no Redlock): lease = SET NX PX, fencing token = INCR of a per-key counter.
The counter never expires (it must only grow), so one small key per lock key is kept forever.
Caveats: Redis replication is async, so after a failover a lease (or counter increments) can be lost: two holders,
or a repeated fencing token. For tokens to survive restarts, run with AOF (appendfsync everysec or always).
The protected resource should still reject writes whose token is below the highest it has seen.
RedisLockStore({
RedisStoreOptions.client: RedisClient
client:
constredis:RedisClient
redis }),
LockOptions.ttl?: Duration |undefined
Lease length. Renewed every ttl/3 while the callback runs. Default 30 s.
Fencing token of the innermost lock the calling code runs under. Pass it to the protected resource so it can reject stale writers.
getLockToken(),
signal: AbortSignal
Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.
signal));
Lease:SET <prefix>{lock:<key>} <owner> NX PX <ttl>, renewed every ttl / 3 while the callback runs. Renewal and release check the owner inside the script, so an instance whose lease expired can’t extend or delete someone else’s.
Fencing token:INCR of a per-key counter, in the same script as the lease, so tokens follow lease order exactly. Pass it to the resource you protect and have it reject writes with a token lower than the highest it has seen. The counter key never expires (it must only grow): one small key per lock key.
RedisRateLimiter is a segmented sliding window (the same algorithm as the in-memory sliding window limiter) stored in a Redis hash. It runs on the Redis server’s clock (TIME), so instances with skewed clocks still agree on the window. Run it with RateLimiterStrategy, which adds waiting, queueing and the other rate limiter options:
import {
classRateLimiterStrategy<T>
Runs any RateLimiterAlgorithm as a strategy: weights, FIFO waiting up to queueTimeout, block, allow/deny, insurance, telemetry.
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().
limiter factory for PartitionedRateLimiterStrategy: one Redis-backed limit per partition key, shared by every instance.
Evicting an idle partition only drops the local object; its count lives on in Redis until the window passes.
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().
Shared bag for passing data between strategies and the callback.
properties.
Map<string, unknown>.get(key: string): unknown
Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
limiter factory for PartitionedRateLimiterStrategy: one Redis-backed limit per partition key, shared by every instance.
Evicting an idle partition only drops the local object; its count lives on in Redis until the window passes.
Limit identity: every instance with the same key and prefix shares one budget. Stored at <prefix>rl:<key>.
permitLimit
number
required
Permits per window (integer ≥ 1).
window
Duration
required
Window length.
segmentsPerWindow
number
10
Buckets the window slides by. 1 is a fixed window aligned to the epoch.
prefix
string
'rp:'
redisPartitionLimiter(options) takes the same options without key (the partition key is used), plus the RateLimiterStrategy options (queueTimeout, queueLimit, blockDuration, insurance…). When the partitioned limiter evicts an idle partition, only the local object goes; the count lives on in Redis until the window passes.
A rejection carries retryAfter: the time until enough old segments leave the window.
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().
Shares the breaker's state with other instances (servers, processes) through a store, e.g. RedisCircuitStateStore:
one instance seeing the dependency fail opens the circuit for all of them.
addCircuitBreaker({ extensions: [sharedCircuitState({ store, key: 'payments' })] })
sharedCircuitState } from'resilience-pipeline';
import {
classRedisCircuitStateStore
CircuitStateStore over Redis: a write only lands if its updatedAt is newer than the stored one. Keys don't expire.
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().
Background behaviour plugged into the breaker, e.g. circuitHealthCheck({ check, interval }) or
sharedCircuitState({ store, key }) from this package. Kept out of the core breaker so you only bundle what you use.
Shares the breaker's state with other instances (servers, processes) through a store, e.g. RedisCircuitStateStore:
one instance seeing the dependency fail opens the circuit for all of them.
addCircuitBreaker({ extensions: [sharedCircuitState({ store, key: 'payments' })] })
Every state change is written to <prefix>cb:<key>, and every instance pulls the shared state every syncInterval (default 5 s). The write is a compare-and-set on the snapshot’s updatedAt, so a slow instance can’t overwrite a newer state with an older one. Each instance’s rolling window of call results stays local. Store errors are reported as OnCircuitStoreError telemetry and don’t fail calls. Keys don’t expire.
The lock’s two keys share the hash tag {<key>}, so they land in the same slot. A {…} in prefix would become the hash tag instead and put every lock in one slot.
Each call sends the script text with EVAL (no EVALSHA caching). That is a few hundred bytes per call; it only matters at very high call rates.