Retry with Exponential Backoff
matureSummary
Retry transient failures with exponentially increasing wait times and added jitter to prevent synchronized retry storms while ensuring eventual operation success without saturating a recovering dependency.
Problem
Transient failures in distributed systems cause request loss when not retried, but naive immediate retries amplify load on a recovering dependency and can cause retry storms that prevent recovery.
Description
Transient failures: network blips, brief database overload, momentary downstream unavailability: are ubiquitous in distributed systems. A fixed immediate retry amplifies the problem: if 1000 clients fail simultaneously and all retry immediately, the recovering dependency receives 1000 requests in the same instant, extending or deepening the failure.
Exponential backoff: after the first failure, wait 100ms. After the second, 200ms. After the third, 400ms. The wait doubles each attempt up to a configured ceiling (typically 30–60 seconds). This gives the dependency breathing room between retry waves.
Jitter: add a random factor to the wait time. Without jitter, all clients that failed at the same moment back off for the same duration and retry simultaneously, recreating the original spike. With full jitter (uniform random from 0 to the computed backoff), retries are spread across the backoff window, dramatically reducing the peak retry load.
AWS recommends "full jitter" (random between 0 and computed_sleep) or "decorrelated jitter" (random between base and previous_sleep × 3) based on empirical benchmarks showing either outperforms equal jitter at scale.
Max attempts: always set a finite limit (e.g., 3–5 attempts). Infinite retry loops consume resources indefinitely and mask permanent failures. Operations that exhaust retries should fail to a dead-letter queue for human inspection or fall back to a degraded response.
Idempotency requirement: retries assume the operation can be safely repeated. A charge $100 call cannot be retried blindly: the second attempt may also succeed, charging the customer twice. Non-idempotent operations require idempotency keys (a client-generated unique token that the server uses to detect and deduplicate repeated requests).
Tradeoffs
Significantly improves success rate for network blips and brief overload without code changes in the dependency
Jitter prevents synchronized retry storms that would reinjure a recovering dependency
Retried calls add significant latency to the tail; p99/p999 latency increases substantially
Every retried operation must be idempotent or use idempotency keys; adds implementation overhead
Without max attempts, retry loops cause resource exhaustion on permanent failures
When to use
Failures are transient (network timeout, brief overload, rate limit)
Retry is only beneficial when the failure condition is temporary and will resolve
Operations are idempotent or can be made idempotent with idempotency keys
Retrying non-idempotent operations without deduplication causes double-execution
Latency budget accommodates retry wait time
Exponential backoff adds significant latency; not suitable when p99 SLO is <100ms
When not to use
Failures are permanent (invalid input, authorization failure, schema error)
Retrying a 400 or 403 response is wasteful and will never succeed
Operation is not idempotent and cannot be wrapped in idempotency key
Retrying non-idempotent mutations without deduplication produces incorrect state
Downstream dependency has a rate limit that is already being exceeded
Retrying under rate limit pressure further reduces effective throughput; use queue-based decoupling
Operational Requirements
Set a finite max_attempts limit for every retry policy
Unbounded retries on permanent failures exhaust connections and threads; 3–5 attempts is typical
Apply jitter (full or decorrelated) to all retry wait calculations
Without jitter, simultaneous retries from multiple clients recreate the original load spike
Route exhausted retries to a dead-letter queue or structured error handler
Silently discarding messages after max retries hides failures; DLQ enables operator inspection and replay
Use idempotency keys for all non-idempotent operations subject to retry
Charge, create, and transfer operations must be deduplicated server-side before applying retry
Characteristics
Technologies
Canonical
Alternatives
Relationships
Evolves to
Complements
Basis
Universally applied pattern; jitter-based backoff is a published AWS best practice with empirical benchmarks
Used In Architecture Scenarios
Write-Heavy Application
A durable background job execution platform where jobs are enqueued via API and executed by competing consumer worker pools. PostgreSQL is the durable job store, job definitions, retry state, scheduling metadata, and dead-letter records persist in PostgreSQL with ACID guarantees, surviving any worker or queue infrastructure failure. Redis tracks in-flight job state (which worker claimed which job, visibility timeout lease expiry) to enable fast lease checks without PostgreSQL queries on the hot path. Temporal provides workflow orchestration for multi-step jobs that require coordination across multiple execution stages, with built-in state machine semantics and durable activity execution. Kafka carries job completion events to downstream consumers (analytics, billing triggers, notification fan-out). Backpressure between the job enqueue rate and worker execution rate prevents runaway job accumulation when workers are degraded.
Event-Driven System
A multi-channel notification delivery architecture that accepts upstream business events (order placed, payment received, comment posted, threshold alert triggered) and routes them to per-channel delivery workers (push via FCM/APNs, email via SendGrid, SMS via Twilio, in-app via WebSocket). Kafka carries raw business events from upstream producers. RabbitMQ handles per-channel fan-out with separate exchanges and queues per delivery channel, isolating email queue backlog from push notification delivery. PostgreSQL provides durable notification state tracking (sent, failed, bounced, suppressed). Redis enforces per-user rate limiting (notification frequency caps to prevent fatigue) and stores deduplication tokens to prevent duplicate sends across retry attempts. The inbox pattern on the consumer side ensures idempotent delivery even when Kafka produces duplicate events.