RoR Background Jobs
Some tasks take too long to run during a web request — sending emails, resizing images, calling external APIs, generating reports, or processing large files. Running these in the background means users get an instant response while Rails handles the heavy work separately. Sidekiq is the most widely used background job processor for Rails.
Why Background Jobs Matter
WITHOUT background jobs: User clicks "Export Report" Browser waits... Rails generates report (30 seconds) Browser waits... User gets the file Total user wait: 30 seconds — bad experience WITH background jobs: User clicks "Export Report" Rails queues the job instantly Browser gets: "Your report is being prepared. Check your email." Total user wait: < 1 second — great experience Report generates in the background, email sent when ready
How Sidekiq Works
Your Rails App Redis Sidekiq Workers
| | |
| Push job to queue ──────► |
| | |
| Respond to user | Worker polls queue ─── ►
| | |
Worker runs the job
(sends email, processes file)
Redis acts as the message queue between your Rails app and Sidekiq workers. Sidekiq runs as a separate process and continuously picks up jobs from the queue.
Install Sidekiq
Gemfile: gem "sidekiq" bundle install
Install Redis (required by Sidekiq):
macOS: brew install redis brew services start redis Ubuntu: sudo apt install redis-server sudo systemctl start redis
Configure Rails to use Sidekiq for Active Job:
config/application.rb config.active_job.queue_adapter = :sidekiq
Create a Background Job
rails generate job SendWelcomeEmail
app/jobs/send_welcome_email_job.rb
class SendWelcomeEmailJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
UserMailer.welcome_email(user).deliver_now
rescue ActiveRecord::RecordNotFound
Rails.logger.warn "User #{user_id} not found. Skipping welcome email."
end
end
Always pass an ID (not the object) to jobs. The job runs later, and the object passed at enqueue time may have changed or been deleted by then.
Enqueue Jobs
Enqueue immediately (runs as soon as a worker is free): SendWelcomeEmailJob.perform_later(user.id) Run after a delay: SendWelcomeEmailJob.set(wait: 10.minutes).perform_later(user.id) Run at a specific time: SendWelcomeEmailJob.set(wait_until: Date.tomorrow.noon).perform_later(user.id) Run in a specific queue: SendWelcomeEmailJob.set(queue: :critical).perform_later(user.id)
A Practical Example — Image Processing Job
app/jobs/process_uploaded_image_job.rb
class ProcessUploadedImageJob < ApplicationJob
queue_as :images
def perform(product_id)
product = Product.find(product_id)
return unless product.featured_image.attached?
product.featured_image.variant(
resize_to_fill: [800, 600],
format: :webp,
quality: 85
).processed ← forces variant generation now
product.update(image_processed: true)
Rails.logger.info "Image processed for product #{product_id}"
end
end
Call it after upload in the controller:
if @product.save
ProcessUploadedImageJob.perform_later(@product.id)
redirect_to @product, notice: "Product saved. Image processing in background."
end
Start Sidekiq
Run Sidekiq in a separate terminal alongside your Rails server:
bundle exec sidekiq
Sidekiq starts workers and begins processing queued jobs immediately.
Sidekiq Web Dashboard
Mount the Sidekiq web UI to monitor jobs, retries, and failed jobs:
config/routes.rb
require "sidekiq/web"
authenticate :user, ->(u) { u.admin? } do
mount Sidekiq::Web => "/sidekiq"
end
Visit http://localhost:3000/sidekiq to see:
Sidekiq Dashboard +-- Busy: 2 jobs currently running +-- Enqueued: 14 jobs waiting +-- Processed: 1,452 jobs completed +-- Failed: 3 jobs failed +-- Queues: default (10), critical (2), images (2) +-- Retries: 3 jobs scheduled for retry
Retries and Error Handling
Sidekiq automatically retries failed jobs with exponential backoff:
class ReportGeneratorJob < ApplicationJob
queue_as :reports
retry_on StandardError, wait: :exponentially_longer, attempts: 5
discard_on ActiveRecord::RecordNotFound
def perform(report_id)
report = Report.find(report_id)
ReportService.new(report).generate!
end
end
retry_on → retries on specific errors (up to 5 times with increasing delay) discard_on → silently discards the job if the record no longer exists
Queues — Prioritize Work
config/sidekiq.yml
:queues:
- [critical, 3] ← processed 3x more often than default
- [default, 2]
- [low, 1]
Use queues in jobs:
class PaymentJob < ApplicationJob
queue_as :critical
end
class NewsletterJob < ApplicationJob
queue_as :low
end
Scheduled Jobs (Recurring)
For jobs that run on a schedule (like a daily digest), use the sidekiq-scheduler or whenever gem:
gem "sidekiq-scheduler"
config/sidekiq.yml
:scheduler:
:schedule:
daily_digest:
cron: "0 8 * * *" ← every day at 8 AM
class: DailyDigestJob
cleanup_expired_sessions:
cron: "0 0 * * *" ← every day at midnight
class: CleanupSessionsJob
Background jobs are essential for production Rails applications. Any task that takes more than a second to complete belongs in a background job. Sidekiq's reliability, speed, and dashboard make it the industry standard choice.
