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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Retrying only a curated list of transient errors avoids masking real bugs that should fail fast.
- 2Exponential backoff capped by a maximum delay prevents both thundering retries and unbounded waits.
- 3Adding jitter spreads out retries from many clients so they don't all reconnect in sync.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/exponential-backoff-retries-in-ruby-explained-ruby-1d7b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.