In today's fast-paced digital world, applications need to handle numerous time-consuming tasks while keeping users engaged. Imagine you're using a popular photo-sharing app:
All of this happens without making you wait! This is the magic of background job processing.
Think of background jobs like running a busy restaurant kitchen:
# Gemfile
gem 'sidekiq'
gem 'sidekiq-scheduler'
gem 'sidekiq-failures'
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = {
url: ENV['REDIS_URL'],
ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE }
}
config.on(:startup) do
Sidekiq::Scheduler.reload_schedule!
end
config.death_handlers << ->(job, ex) do
DeadJobNotifier.notify(job, ex)
end
end
Sidekiq.configure_client do |config|
config.redis = {
url: ENV['REDIS_URL'],
ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE }
}
end
# config/sidekiq.yml
:concurrency: <%= ENV.fetch("SIDEKIQ_CONCURRENCY", 5) %>
:queues:
- [critical, 3]
- [default, 2]
- [low, 1]
:scheduler:
:schedule:
daily_report:
cron: '0 0 * * *'
class: DailyReportJob
cleanup_old_data:
cron: '0 3 * * *'
class: CleanupJob
# app/jobs/image_processing_job.rb
class ImageProcessingJob < ApplicationJob
queue_as :critical
def perform(image_id)
image = Image.find(image_id)
process_image(image)
notify_user(image.user)
rescue StandardError => e
handle_error(e, image)
end
private
def process_image(image)
# Create different sizes for web, mobile, and thumbnails
variants = [
{ resize: "800x800>" }, # Web size
{ resize: "400x400>" }, # Mobile size
{ resize: "200x200>" } # Thumbnail
]
variants.each do |variant|
image.file.variant(variant).processed
end
end
def notify_user(user)
NotificationJob.perform_later(
user_id: user.id,
message: "Your image has been processed!"
)
end
def handle_error(error, image)
ErrorTracker.capture_exception(
error,
extra: { image_id: image.id }
)
image.update(status: :processing_failed)
raise error
end
end
# config/sidekiq.yml
:scheduler:
:schedule:
# Morning cleanup (like preparing the kitchen for the day)
daily_cleanup:
cron: '0 0 * * *' # Run at midnight
class: CleanupJob
queue: maintenance
description: "Clean up old records and files"
# Regular health checks (like checking food temperatures)
hourly_metrics:
every: '1h'
class: MetricsCollectionJob
queue: metrics
description: "Collect system metrics"
# Weekly planning (like planning next week's menu)
weekly_report:
cron: '0 9 * * MON' # Run at 9 AM every Monday
class: WeeklyReportJob
queue: reporting
description: "Generate weekly reports"
Scenario: An e-commerce site during a sale
Solution:
# app/jobs/order_processing_job.rb
class OrderProcessingJob < ApplicationJob
queue_as :critical
def perform(order_id)
order = Order.find(order_id)
# Process in smaller chunks
order.items.in_batches(of: 100) do |batch|
process_batch(batch)
end
end
end
Scenario: Generating monthly reports
Solution:
# app/jobs/report_generation_job.rb
class ReportGenerationJob < ApplicationJob
queue_as :reporting
def perform(report_params)
# Break into smaller jobs
report_params[:date_ranges].each do |date_range|
ReportChunkJob.perform_later(date_range)
end
end
end
Scenario: Failed payment processing
Solution:
# app/jobs/payment_processing_job.rb
class PaymentProcessingJob < ApplicationJob
retry_on PaymentError, wait: :exponentially_longer, attempts: 3
def perform(payment_id)
payment = Payment.find(payment_id)
begin
process_payment(payment)
rescue PaymentError => e
notify_admin(payment, e)
raise # Trigger retry
end
end
end
# app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
around_perform :track_performance
private
def track_performance
start_time = Time.current
yield
duration = Time.current - start_time
StatsD.timing(
"jobs.#{self.class.name}.duration",
duration * 1000
)
end
end
# app/services/job_health_monitor.rb
class JobHealthMonitor
def self.check_health
{
queues: queue_sizes,
processing: currently_processing,
failed: failed_count,
scheduled: scheduled_count
}
end
end
Background job processing is like having a well-organized team working behind the scenes in your application. When implemented correctly, it:
Remember: Just like a restaurant kitchen needs to adapt to different volumes of customers and types of orders, your background job system should be flexible and robust enough to handle varying workloads and requirements.
Join our community of developers and get weekly insights, tutorials, and best practices delivered straight to your inbox
Master the principles of API design and domain architecture to create scalable, maintainable applications using the digital city metaphor.