ruby 29 lines · 7 steps

Exponential backoff retries in Ruby

A reusable module that retries a block on transient network errors with exponentially growing, jittered delays.

Explained by highlit
1module Retryable
2 class RetriesExhausted < StandardError; end
3 
4 RETRYABLE_ERRORS = [
5 Net::OpenTimeout,
6 Net::ReadTimeout,
7 Errno::ECONNRESET,
8 Errno::ECONNREFUSED,
9 SocketError
10 ].freeze
11 
12 def self.with_backoff(max_attempts: 5, base_delay: 0.5, max_delay: 30.0, jitter: true)
13 attempt = 0
14 
15 begin
16 attempt += 1
17 yield(attempt)
18 rescue *RETRYABLE_ERRORS => error
19 raise RetriesExhausted, "gave up after #{attempt} attempts: #{error.message}" if attempt >= max_attempts
20 
21 delay = [base_delay * (2**(attempt - 1)), max_delay].min
22 delay += rand * delay if jitter
23 
24 warn "attempt #{attempt} failed (#{error.class}: #{error.message}); retrying in #{delay.round(2)}s"
25 sleep(delay)
26 retry
27 end
28 end
29end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Retrying only a curated list of transient errors avoids masking real bugs that should fail fast.
  2. 2Exponential backoff capped by a maximum delay prevents both thundering retries and unbounded waits.
  3. 3Adding jitter spreads out retries from many clients so they don't all reconnect in sync.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Exponential backoff retries in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code