TypeScript ยท Zero runtime dependencies ยท npm ยท Source

Polite Retry

Retries that don't overwhelm your servers

A smart retry library for TypeScript that prevents retry amplification and cascading failures in distributed systems.

npm install polite-retry

Why Polite Retry?

๐Ÿ›ก๏ธ

Prevents Retry Storms

Adaptive Retry Budgeting limits retry volume to prevent cascading failures when services are struggling.

โšก

Circuit Breaker

Built-in circuit breaker pattern stops requests to failing services, allowing them time to recover.

๐ŸŽฒ

Smart Jitter

Multiple jitter strategies prevent synchronized retries that cause periodic load spikes.

๐Ÿ“Š

Backpressure Aware

Respects backpressure signals from downstream services to avoid overwhelming them.

๐Ÿ“ˆ

Built-in Metrics

Track retry rates, success rates, and retry amplification factor for observability.

๐Ÿ”ง

TypeScript First

Full TypeScript support with comprehensive type definitions and excellent IDE integration.

The Problem: Retry Amplification

When a service experiences partial failure, naive retry policies can make things much worse:

  1. Service starts failing 50% of requests
  2. All clients retry failed requests
  3. Service now receives 2x the load
  4. More requests fail, triggering more retries
  5. Cascade collapse

In a 3-tier system with 50% failure rate and 3 retries per tier, request volume can amplify by 6.6x.

Normal:     100 req โ†’ Service โ†’ 100 responses โœ“

With naive retries during 50% failure:
            100 req โ”€โ”€โ”
            100 retry โ”ผโ”€โ”€โ–บ Service โ”€โ”€โ–บ Overload! ๐Ÿ’ฅ
            100 retry โ”˜
          

The Solution: Adaptive Retry Budgeting

Polite Retry implements the ARB algorithm that dynamically limits retries based on observed failure rates:

import { retryWithBudget, AdaptiveRetryBudget } from 'polite-retry';

// Create a shared budget (one per downstream service)
const budget = new AdaptiveRetryBudget({
  initialBudget: 0.2,        // Allow 20% retry overhead
  highFailureThreshold: 0.3, // Reduce budget when >30% failing
});

// All requests share this budget
const data = await retryWithBudget(
  async () => {
    const res = await fetch('https://api.example.com/data');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  },
  budget,
  { maxRetries: 3, jitter: 'full' }
);

// Check metrics
console.log(budget.getMetrics());
// { retryAmplificationFactor: 1.15, failureRate: 0.08, ... }

Choose Your Strategy

Strategy Use Case Amplification Risk Complexity
retry() Simple retries with backoff Medium Low
retryWithCircuitBreaker() Stop when service is down Low Medium
retryWithBudget() Production microservices Very Low Medium
retryWithProtection() Critical systems Very Low Higher

Quick Start

import { retry } from 'polite-retry';

// Basic retry with exponential backoff and jitter
const data = await retry(
  async () => {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  },
  {
    maxRetries: 3,
    initialDelayMs: 100,
    jitter: 'full', // Prevents synchronized retry storms
    onRetry: (error, attempt) => {
      console.log(`Attempt ${attempt} failed: ${error.message}`);
    }
  }
);