Files
skills/docs/foundation/flow/retry/src/strategies/exponential/exponential.md
2026-05-29 08:20:56 +01:00

4.0 KiB
Raw Blame History

exponential

(no submodules)

Purpose

Provides createExponentialBackoff, applyJitter, and clampDelay — the three core building blocks for exponential backoff with optional jitter. Exponential backoff is the industry-standard strategy for retrying failed calls against overloaded or degraded services: each subsequent delay doubles (or grows by configurable base), reducing collision probability under high concurrency far better than constant or linear strategies.

Jitter breaks the synchronization problem that arises when many clients retry at identical intervals, which can cause "thundering herd" effects that worsen rather than relieve overloaded services. This module supports four jitter modes aligned with the AWS/Google SRE recommendations: full, equal, decorrelated, and none.

Features

  • Configurable initial delay, base exponent (default 2), and max cap
  • Four jitter modes: full, equal, decorrelated, none
  • Pluggable Rng for deterministic testing — falls back to Math.random()
  • applyJitter usable independently on any delay value
  • clampDelay utility for explicit range enforcement
  • All functions are pure (no side effects beyond rng call)

Usage: Basic Example

import { createExponentialBackoff, withRetry } from "@suaveplan/retry";

const strategy = createExponentialBackoff({ initial: 100, max: 30_000, jitter: "full" });

// attempt 1 → ~0–100ms, attempt 2 → ~0–200ms, etc.
await withRetry(fetchQuote, { maxAttempts: 6, delay: strategy });

Usage: Advanced Example

import { createExponentialBackoff, applyJitter, clampDelay } from "@suaveplan/retry";

// Seeded RNG for deterministic tests
const rng = { nextUint32: () => 2147483648, nextFloat: () => 0.5 };

const strategy = createExponentialBackoff(
  { initial: 50, base: 3, max: 10_000, jitter: "equal" },
  rng,
);

// Manual composition: clamp after jitter for extra safety
const bounded = (attempt: number) => clampDelay(strategy(attempt), 50, 5_000);

await withRetry(writeRecord, {
  maxAttempts: 8,
  delay: bounded,
  shouldRetry: (err) => isTransient(err),
  signal: AbortSignal.timeout(60_000),
});

API Reference

createExponentialBackoff(opts, rng?)

Parameter Type Description
opts.initial number Base delay for attempt 1 (ms).
opts.base number? Exponent base. Default: 2.
opts.max number? Maximum delay cap (ms).
opts.jitter JitterMode? Jitter mode. Default: "none".
rng Rng? Random source. Falls back to Math.random().

Returns: RetryStrategy — (attempt: number) => number.

applyJitter(delay, mode, rng?)

Parameter Type Description
delay number Base delay to apply jitter to (ms).
mode JitterMode One of "full", "equal", "decorrelated", "none".
rng Rng? Random source. Falls back to Math.random().

Returns: number — jitter-adjusted delay.

clampDelay(delay, min, max)

Parameter Type Description
delay number Unclamped delay (ms).
min number Inclusive lower bound.
max number Inclusive upper bound.

Returns: number — Math.max(min, Math.min(max, delay)).

Implementation Notes

The createExponentialBackoff factory captures all configuration in a closure and calls applyJitter internally if a jitter mode other than "none" is configured. This ensures the correct rng is threaded through without callers needing to pass it on every call. The "decorrelated" jitter mode is a simplified variant that maps delay to [delay, 2*delay] — the classic AWS decorrelated formula uses rand(delay/2, prev*3) but requires external state; this simplified form achieves similar dispersion without statefulness. clampDelay is intentionally a standalone function rather than part of the strategy to keep concerns separated and allow explicit composition.