ActiveJob and Sidekiq in Rails
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 raiseddiscard_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
- Rails Middleware — Understanding the request processing pipeline
- ActionCable and WebSockets — Real-time communication in Rails
- Rails Caching Strategies — Performance optimization techniques