Practice · Scaling · Card 7
The signup is slow. What moves first?
A signup page that takes 3 seconds to respond. The user is waiting on something they don't need to see complete.
The action
The user posts the signup form. The page takes about 3 seconds before redirecting.
def create
@user = User.new(user_params)
if @user.save
WelcomeMailer.with(user: @user).welcome_email.deliver_now
SegmentClient.track_signup(@user)
Slack.notify("#new-signups", "#{@user.email} just signed up")
redirect_to dashboard_path
else
render :new
end
end The question
Which side effects belong on the synchronous path? Which should move first? What's the question to ask of each one?
Take a moment. For each side effect, ask: does the user need to see this complete before they get their response? The answer for each one decides the synchronous vs background split.
The right move
Move the email, the Segment event, and the Slack notification into background jobs. The user doesn't need any of those three to complete before seeing the dashboard. They are slow, networked, and failure-tolerant — perfect candidates.
def create
@user = User.new(user_params)
if @user.save
WelcomeMailer.with(user: @user).welcome_email.deliver_later
SegmentJob.perform_later(@user.id)
SlackJob.perform_later("#new-signups", "#{@user.email} just signed up")
redirect_to dashboard_path
else
render :new
end
end The action returns in ~50ms instead of 3s. The side effects run on a worker process, with retries, with proper error handling, without keeping the user waiting.
The question to ask
For any side effect inside a controller action, ask one question: does the user need to see this complete before getting their response?
- Yes (saving their data, the answer they need to see) → synchronous.
- No (emails, analytics, notifications, thumbnails, indexing) → background job.
Wrong moves
- "Add a loading spinner." Makes the 3s wait visible, not absent. User is still blocked. Solve the wait, don't decorate it.
- "Wrap the side effects in a Thread." Threads in a request cause connection-pool exhaustion, lose exceptions, and don't survive the request finishing cleanly. Background jobs are Threads-done-right with retries, observability, and isolation.
- "Increase the Puma timeout." Hiding the slow response by raising the timeout doesn't make it fast. It worsens degradation under load — workers stay busy longer, queue grows, everyone is slower.
The principle
Background jobs aren't a performance tool, they're a responsibility-boundary tool. The user is responsible for one outcome (their signup). The system is responsible for the rest. Crossing that boundary into the synchronous path is what makes pages slow.
Theory
Full walkthrough at Scaling · Background Jobs as a Scaling Tool.