Background Jobs with Active Job & Sidekiq

Reviewed & published by Brayan K

Slow work — sending email, resizing images, calling external APIs — should never block a web request. Background jobs move that work off the request cycle so your app stays fast.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll declare jobs with Active Job, enqueue them with perform_later, pick a backend like Sidekiq, and handle retries, scheduling, and idempotency.

What You'll Learn in This Lesson

1️⃣ Declaring and Enqueuing Jobs

An Active Job class inherits from ApplicationJob and does its work in perform. Enqueue it with perform_later so the request returns fast; perform_now runs it inline.

# Active Job gives every job a common shape, whatever backend runs it.
# Generate one with:  rails generate job send_welcome_email

class SendWelcomeEmailJob < ApplicationJob
  queue_as :default              # which queue this job uses

  def perform(user_id)           # the work to do, given arguments
    user = User.find(user_id)
    UserMailer.welcome(user).deliver_now
  end
end

# In a controller, ENQUEUE it instead of doing the work inline:
class UsersController < ApplicationController
  def create
    user = User.create!(user_params)
    SendWelcomeEmailJob.perform_later(user.id)  # async — returns fast
    redirect_to user
  end
end

# perform_later  -> enqueue, run later by a worker
# perform_now    -> run immediately, in this process (rarely in web)

2️⃣ Backends, Scheduling, and Retries

Active Job is the interface; a backend actually runs the jobs. Configure one adapter (:sidekiq, :solid_queue, and others), then use scheduling and retry declarations.

# Active Job is just the INTERFACE; a backend actually runs jobs.
# Configure one in config/application.rb:

config.active_job.queue_adapter = :sidekiq   # uses Redis
# Other choices: :solid_queue (database), :resque, :delayed_job,
#                :async (in-process, dev only)

# Scheduling and retries are first-class:
ReportJob.set(wait: 1.hour).perform_later         # run in an hour
ReportJob.set(wait_until: Date.tomorrow.noon).perform_later

class FlakyApiJob < ApplicationJob
  retry_on Net::OpenTimeout, wait: 5.seconds, attempts: 3
  discard_on ActiveJob::DeserializationError       # give up cleanly
  def perform; end
end

# Start a Sidekiq worker process with:  bundle exec sidekiq

Your turn. Finish the resize job's perform body, then enqueue it asynchronously.

# 🎯 YOUR TURN — finish the job, then enqueue it.

class ResizeAvatarJob < ApplicationJob
  queue_as :images

  def perform(user_id)
    user = User.find(user_id)
    # 👉 TODO: do the slow work here (e.g. resize the image)
    user.avatar.resize!("128x128")
  end
end

# Enqueue it asynchronously so the web request stays fast:
ResizeAvatarJob.perform_later(user.id)

# ✅ Expected log line:
#    Enqueued ResizeAvatarJob to ...(images) with arguments: <user_id>

Mini-Challenge: An Idempotent Job

Design a scheduled NightlyReportJob that is safe to retry — re-running it for the same date must not create duplicates.

# 🎯 MINI-CHALLENGE: design an idempotent job.
# Sketch (no need to run) a NightlyReportJob that:
#
# 1. Inherits from ApplicationJob and uses queue_as :reports.
# 2. Takes a date argument in perform(date).
# 3. Is IDEMPOTENT — guard so re-running for the same date does not
#    create a duplicate report (e.g. find_or_create_by).
# 4. Is scheduled with: NightlyReportJob.set(wait_until: ...).perform_later
#
# ✅ Goal: safe to retry, runs off the request cycle.

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Background Jobs

ConceptRails Syntax
Define a jobclass X < ApplicationJob
Enqueue (async)X.perform_later(id)
Run now (sync)X.perform_now(id)
ScheduleX.set(wait: 1.hour)
Retry on errorretry_on SomeError
Choose queuequeue_as :default

🎉 Lesson Complete!

Practice quiz

Why move slow work into a background job?

  • to keep the web request fast by doing the work asynchronously
  • to avoid writing tests
  • to bypass routing
  • to make the database larger

Answer: to keep the web request fast by doing the work asynchronously. Long tasks like email or image processing run off the request cycle so the user gets a fast response.

What is Active Job in Rails?

  • a database adapter
  • a templating engine
  • a framework for declaring jobs and running them on various backends
  • a gem for CSS

Answer: a framework for declaring jobs and running them on various backends. Active Job is a Rails framework that provides a common interface for background jobs across backends.

Which class do Active Job jobs typically inherit from?

  • ActionController::Base
  • ApplicationJob
  • ActiveJob::Runner
  • ApplicationRecord

Answer: ApplicationJob. Generated jobs inherit from ApplicationJob, which inherits from ActiveJob::Base.

Which method enqueues a job to run asynchronously?

  • perform_now
  • call_async
  • run_background
  • perform_later

Answer: perform_later. perform_later enqueues the job; perform_now would run it immediately in the current process.

What does perform_now do?

  • runs the job immediately in the current process
  • retries the job
  • schedules the job for tomorrow
  • deletes the job

Answer: runs the job immediately in the current process. perform_now executes the job synchronously right away rather than enqueuing it.

Which popular backend uses Redis to store and process jobs?

  • Nginx
  • Sidekiq
  • SQLite
  • Memcached

Answer: Sidekiq. Sidekiq is a widely used background processing library that relies on Redis.

Which newer backend stores jobs in your relational database (no Redis)?

  • Resque
  • Delayed Job classic
  • Faktory
  • Solid Queue

Answer: Solid Queue. Solid Queue is a database-backed Active Job backend, now a Rails default.

What does it mean for a job to be idempotent?

  • it never fails
  • it ignores arguments
  • running it more than once has the same effect as running it once
  • it can only run once ever

Answer: running it more than once has the same effect as running it once. An idempotent job is safe to retry because repeating it does not cause duplicate side effects.

How do you schedule a job to run later with Active Job?

  • MyJob.delay(1.hour)
  • MyJob.set(wait: 1.hour).perform_later
  • MyJob.sleep(1.hour)
  • MyJob.after(1.hour)

Answer: MyJob.set(wait: 1.hour).perform_later. set(wait: 1.hour).perform_later (or wait_until:) schedules the job for the future.

Why do background backends retry failed jobs automatically?

  • to recover from transient errors like a brief network outage
  • to skip validations
  • to slow the app down
  • to delete the queue

Answer: to recover from transient errors like a brief network outage. Automatic retries help jobs survive temporary failures; idempotency makes those retries safe.

Continue this course

Frequently asked questions

Why should long tasks run off the web request cycle?

Web requests should return quickly so the user is not left staring at a spinner, and so your server can handle many requests at once. Tasks like sending email, generating PDFs, processing images, calling slow third-party APIs, or crunching reports can take seconds or minutes — far too long to block a request. By enqueuing them as background jobs, the controller finishes immediately and a separate worker process does the heavy lifting later. This keeps the app responsive, prevents request timeouts, and lets you scale workers independently of web servers when the workload grows.

What does Active Job actually do?

Active Job is the Rails framework that gives background jobs a single, consistent interface regardless of which queuing backend you use. You write a class that inherits from ApplicationJob with a perform method, then enqueue it with perform_later. Active Job handles serializing arguments, choosing a queue with queue_as, scheduling with set(wait:), and declaring retries with retry_on or discard_on. The key benefit is portability: the same job code runs on Sidekiq, Solid Queue, Resque, or others, so you can switch backends by changing one configuration line rather than rewriting every job.

Which backend should I choose: Sidekiq, Solid Queue, or others?

Sidekiq is the long-standing favorite for high throughput; it stores jobs in Redis and processes them efficiently with threads, and has a rich ecosystem and dashboard. Solid Queue is a newer, database-backed backend that became a Rails default, attractive because it needs no extra Redis service. Resque and Delayed Job are older options still used in many codebases; Delayed Job is database-backed and simple, while Resque also uses Redis. The right pick depends on scale and infrastructure: if you already run Redis and need high volume, Sidekiq is excellent; if you want fewer moving parts, Solid Queue keeps everything in your existing database.

What is idempotency and why do jobs need it?

An idempotent job produces the same result whether it runs once or several times, so re-running it causes no extra side effects. This matters because background systems retry failed jobs automatically — to recover from transient problems like a brief network outage — and a job can occasionally run more than once even after appearing to succeed. If a payment or email job is not idempotent, a retry could charge a card twice or send duplicate messages. You make jobs idempotent by guarding their effects: check whether the work is already done, use unique constraints or find_or_create_by, and design operations so repeating them is safe.