Initial Commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# exponential
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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.
|
||||
Reference in New Issue
Block a user