DBRaven
Pattern · resilience

Retry with Exponential Backoff

mature

Summary

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

Transient failure recovery
+0.8

Significantly improves success rate for network blips and brief overload without code changes in the dependency

Thundering herd prevention
+0.8

Jitter prevents synchronized retry storms that would reinjure a recovering dependency

Latency tail
-0.5

Retried calls add significant latency to the tail; p99/p999 latency increases substantially

Idempotency burden
-0.3

Every retried operation must be idempotent or use idempotency keys; adds implementation overhead

Infinite loop risk
-0.4

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

mandatory

Set a finite max_attempts limit for every retry policy

Unbounded retries on permanent failures exhaust connections and threads; 3–5 attempts is typical

mandatory

Apply jitter (full or decorrelated) to all retry wait calculations

Without jitter, simultaneous retries from multiple clients recreate the original load spike

recommended

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

mandatory

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

Scales on
connections
Implementation complexitylow
Operational complexitylow
Scaling ceilingRetry logic is per-call, not a throughput scaling mechanism. At high concurrency, even with jitter, simultaneous retries from thousands of clients can produce significant load on a recovering dependency. Combine with circuit breaker to stop retrying when the dependency is known to be unavailable.

Technologies

Canonical

resilience4j

Alternatives

pollytenacity

Relationships

Evolves to

circuit breaker

Complements

circuit breakercompeting consumersoutbox pattern

Basis

Universally applied pattern; jitter-based backoff is a published AWS best practice with empirical benchmarks

Used In Architecture Scenarios

Distributed Job Queue Platformmoderate

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.

Notification Delivery Platformmoderate

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.

Retry with Exponential Backoff: DBRaven