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.

Leave a Comment

Your email address will not be published. Required fields are marked *