ActiveJob and Sidekiq in Rails

· 4 min read · Updated March 17, 2026 · intermediate
rails activejob sidekiq background-jobs ruby performance

Background jobs are essential for any Rails application that needs to handle time-consuming tasks without blocking the user request-response cycle. Sending emails, processing images, generating reports, and syncing data with external services are all perfect candidates for background processing.

ActiveJob is Rails’ standardized interface for background jobs, while Sidekiq is one of the most popular and performant job processing libraries. Together, they provide a powerful combination for handling asynchronous work in your Rails applications.

What Is ActiveJob?

ActiveJob is a framework for declaring jobs and making them run on various queueing backends. It provides a uniform API regardless of whether you’re using Sidekiq, Resque, Delayed Job, or another queuing system.

The key benefit is portability. If you switch from Sidekiq to another backend later, your job code doesn’t need to change:

# This works the same regardless of the configured backend
MyJob.perform_later(user)

Setting Up Sidekiq

Add Sidekiq to your Gemfile:

gem 'sidekiq'
gem 'redis'

Run the installation generator:

rails g sidekiq:install

This creates an initializer at config/initializers/sidekiq.rb and sets up the default queue configuration.

Configure your Redis connection in config/sidekiq.yml:

:concurrency: 5
:queues:
  - default
  - mailers
  - [critical, 3]

The [critical, 3] syntax means the critical queue has priority 3 (higher priority runs first).

Creating Your First Job

Generate a new job:

rails g job ProcessReport

This creates app/jobs/process_report_job.rb:

class ProcessReportJob < ApplicationJob
  queue_as :default

  def perform(report_id)
    report = Report.find(report_id)
    report.process!
    ReportMailer.completed(report).deliver_later
  end
end

The queue_as method lets you specify which queue this job belongs to. By default, jobs go to the default queue.

Enqueuing Jobs

There are several ways to enqueue a job:

# Enqueue for immediate processing
ProcessReportJob.perform_later(report.id)

# Enqueue with a delay (Sidekiq handles this)
ProcessReportJob.perform_in(1.hour, report.id)
ProcessReportJob.perform_later(report.id, wait: 30.minutes)

# Enqueue at a specific time
ProcessReportJob.perform_later(report.id, wait_until: tomorrow.midnight)

The deliver_later method used on mailers is actually powered by ActiveJob under the hood, so it goes through the same queueing system.

Job Execution Options

ActiveJob provides several options for controlling job execution:

class MyJob < ApplicationJob
  queue_as :default

  retry_on StandardError, wait: :exponentially_longer, attempts: 5
  discard_on ActiveJob::PerformingJobError

  def perform(*args)
    # Your job logic
  end
end
  • retry_on: Automatically retry the job when an exception is raised
  • discard_on: Don’t retry, just discard the job for specific exceptions

Priority and Queue Configuration

Sidekiq processes queues in order of priority. Lower numbers mean higher priority:

# config/sidekiq.yml
:concurrency: 10
:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]
  - mailers

To specify queue priority when enqueueing:

MyJob.set(queue: :critical).perform_later(args)

Monitoring with the Web UI

Sidekiq provides a built-in web interface for monitoring jobs. Add this to your config/routes.rb:

require 'sidekiq/web'
Rails.application.routes.draw do
  mount Sidekiq::Web => '/sidekiq'
end

Visit /sidekiq in your browser to see:

  • Dashboard: Real-time stats on processed, failed, and enqueued jobs
  • Queues: View jobs waiting in each queue
  • Retries: Jobs that failed and are scheduled for retry
  • Dead: Jobs that exceeded retry attempts
  • Scheduled: Jobs waiting to run at a specific time

Best Practices

Use Unique Jobs

Each job should do one thing. If you need to send three emails, create three separate jobs:

# Bad - tightly coupled
class ProcessUserSignupJob < ApplicationJob
  def perform(user)
    send_welcome_email(user)
    setup_default_preferences(user)
    notify_admin(user)
  end
end

# Good - loosely coupled, each can fail independently
class SendWelcomeEmailJob < ApplicationJob; end
class SetupDefaultPreferencesJob < ApplicationJob; end
class NotifyAdminOfSignupJob < ApplicationJob; end

Store Identifiers, Not Objects

Always pass serializable arguments to jobs:

# Good - passes the user ID
WelcomeMailerJob.perform_later(user.id)

# Bad - passes an ActiveRecord object (may become stale)
WelcomeMailerJob.perform_later(user)

Use Callbacks for Logging

Monitor job execution with callbacks:

class MyJob < ApplicationJob
  before_enqueue :log_start
  around_perform :measure_time
  after_perform :log_completion

  def perform(*args)
    # work
  end

  private

  def measure_time
    start = Time.current
    yield
    Rails.logger.info "Job took #{Time.current - start} seconds"
  end
end

Set Appropriate Timeouts

Sidekiq allows per-job timeouts:

class LongRunningJob < ApplicationJob
  sidekiq_options timeout: 300 # 5 minutes

  def perform
    # This job won't be interrupted for 5 minutes
  end
end

Error Handling

Sidekiq’s probabilistic exponential backoff is the default retry strategy:

class MyJob < ApplicationJob
  retry_on StandardError, wait: :exponentially_longer, attempts: 5
end

This retries at intervals of approximately 10 seconds, 1 minute, 10 minutes, 1 hour, and 10 hours.

For critical jobs, consider using unique jobs to prevent duplicates:

class SendWelcomeEmailJob < ApplicationJob
  sidekiq_options unique_for: 1.hour

  def perform(user_id)
    # Won't run again if already running in the last hour
  end
end

Running Sidekiq

Start the Sidekiq process:

bundle exec sidekiq

In production, use a process manager like systemd or Docker:

CMD bundle exec sidekiq -C config/sidekiq.yml

For local development, run Sidekiq in a separate terminal:

rails server
# In another terminal:
bundle exec sidekiq

Testing Jobs

Use Sidekiq’s testing fake to run jobs synchronously during tests:

require 'sidekiq/testing'

Sidekiq::Testing.inline! # Jobs execute immediately

it 'sends a welcome email' do
  expect {
    UserSignupJob.perform_later(user.id)
  }.to change { ActionMailer::Base.deliveries.count }.by(1)
end

For more control:

Sidekiq::Testing.fake! # Jobs are enqueued but not executed

See Also