Background Job Generator
Generate background jobs using Solid Queue for asynchronous task processing.
Quick Start
code
Task Progress (copy and check off): - [ ] 1. Identify async task needed - [ ] 2. Create job class - [ ] 3. Add job logic - [ ] 4. Enqueue job from controller/model - [ ] 5. Test job execution - [ ] 6. Monitor queue dashboard
Project Setup
Solid Queue (Rails 8 기본 탑재):
ruby
# Gemfile (already included) gem "solid_queue" # config/queue.yml (already configured) # Solid Queue runs in-process or separate worker
Queue Dashboard:
ruby
# config/routes.rb require "solid_queue/engine" mount SolidQueue::Engine => "/queue"
Job Structure
code
app/jobs/ ├── application_job.rb # Base class ├── email_job.rb # Email sending ├── notification_job.rb # Push notifications ├── data_import_job.rb # Data processing └── cleanup_job.rb # Scheduled cleanup
Basic Job Template
ruby
# app/jobs/example_job.rb
class ExampleJob < ApplicationJob
queue_as :default
def perform(arg1, arg2)
# Job logic here
puts "Processing #{arg1} and #{arg2}"
end
end
Enqueue:
ruby
# Execute immediately (in background)
ExampleJob.perform_later("value1", "value2")
# Execute at specific time
ExampleJob.set(wait: 1.hour).perform_later("value1", "value2")
ExampleJob.set(wait_until: Date.tomorrow.noon).perform_later("value1", "value2")
Common Job Types
1. Email Job
ruby
# app/jobs/send_email_job.rb
class SendEmailJob < ApplicationJob
queue_as :default
retry_on Net::SMTPServerBusy, wait: 5.minutes, attempts: 3
def perform(user_id, email_type)
user = User.find(user_id)
case email_type
when "welcome"
UserMailer.welcome_email(user).deliver_now
when "notification"
UserMailer.notification_email(user).deliver_now
else
raise ArgumentError, "Unknown email type: #{email_type}"
end
Rails.logger.info "Sent #{email_type} email to user #{user.id}"
end
end
Usage:
ruby
# In controller or model SendEmailJob.perform_later(user.id, "welcome")
2. Notification Job
ruby
# app/jobs/send_notification_job.rb
class SendNotificationJob < ApplicationJob
queue_as :notifications
def perform(notification_id)
notification = Notification.find(notification_id)
# Send push notification
send_push_notification(notification)
# Mark as sent
notification.update(sent_at: Time.current)
end
private
def send_push_notification(notification)
# Integration with FCM, APNs, etc.
Rails.logger.info "Sending notification #{notification.id}"
end
end
3. Data Processing Job
ruby
# app/jobs/process_import_job.rb
class ProcessImportJob < ApplicationJob
queue_as :heavy
# Retry with exponential backoff
retry_on StandardError, wait: :exponentially_longer, attempts: 5
def perform(file_path)
CSV.foreach(file_path, headers: true) do |row|
User.create!(
email: row["email"],
name: row["name"]
)
end
# Cleanup temp file
File.delete(file_path) if File.exist?(file_path)
rescue StandardError => e
Rails.logger.error "Import failed: #{e.message}"
raise
end
end
4. Scheduled Job (Cleanup)
ruby
# app/jobs/cleanup_old_posts_job.rb
class CleanupOldPostsJob < ApplicationJob
queue_as :maintenance
def perform
deleted_count = Post.where("created_at < ?", 1.year.ago)
.where(status: :draft)
.delete_all
Rails.logger.info "Cleaned up #{deleted_count} old draft posts"
end
end
Schedule with cron:
ruby
# config/recurring.yml (Solid Queue)
production:
cleanup_old_posts:
class: CleanupOldPostsJob
queue: maintenance
schedule: "0 2 * * *" # 2 AM daily
Queue Priority
ruby
# app/jobs/high_priority_job.rb class HighPriorityJob < ApplicationJob queue_as :urgent queue_with_priority 10 # Higher = more priority end # app/jobs/low_priority_job.rb class LowPriorityJob < ApplicationJob queue_as :background queue_with_priority 1 end
Queue Configuration (config/queue.yml):
yaml
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: urgent
threads: 3
processes: 2
polling_interval: 0.1
- queues: default,notifications
threads: 5
processes: 3
- queues: heavy,maintenance
threads: 2
processes: 1
Error Handling
Retry Strategies
ruby
class RetryableJob < ApplicationJob
# Retry specific errors
retry_on ConnectionError, wait: 5.seconds, attempts: 3
# Exponential backoff
retry_on TimeoutError, wait: :exponentially_longer, attempts: 10
# Custom retry logic
retry_on CustomError do |job, exception|
Rails.logger.error "Job #{job.class} failed: #{exception.message}"
Sentry.capture_exception(exception)
end
# Discard on certain errors
discard_on ActiveJob::DeserializationError
def perform
# Job logic
end
end
Error Callbacks
ruby
class MonitoredJob < ApplicationJob
around_perform :log_execution
rescue_from StandardError do |exception|
Rails.logger.error "Job failed: #{exception.message}"
# Send to error tracking service
Sentry.capture_exception(exception)
end
def perform
# Job logic
end
private
def log_execution
start_time = Time.current
Rails.logger.info "Starting job: #{self.class.name}"
yield
duration = Time.current - start_time
Rails.logger.info "Job completed in #{duration}s"
end
end
Testing
ruby
# test/jobs/send_email_job_test.rb
require "test_helper"
class SendEmailJobTest < ActiveJob::TestCase
test "enqueues email job" do
assert_enqueued_with(job: SendEmailJob, args: [users(:one).id, "welcome"]) do
SendEmailJob.perform_later(users(:one).id, "welcome")
end
end
test "sends welcome email" do
assert_emails 1 do
SendEmailJob.perform_now(users(:one).id, "welcome")
end
end
test "handles missing user gracefully" do
assert_raises(ActiveRecord::RecordNotFound) do
SendEmailJob.perform_now(99999, "welcome")
end
end
end
Monitoring
Dashboard
code
Visit: http://localhost:3000/queue - View pending jobs - Monitor failed jobs - See queue statistics
Logging
ruby
# config/environments/production.rb
config.active_job.logger = ActiveSupport::Logger.new("log/jobs.log")
# In jobs
Rails.logger.info "Job started: #{job_id}"
Rails.logger.error "Job failed: #{error_message}"
Metrics
ruby
# app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
around_perform :track_performance
private
def track_performance
start_time = Time.current
yield
ensure
duration = Time.current - start_time
Rails.logger.info "Job #{self.class.name} took #{duration}s"
# Send to monitoring service (e.g., StatsD)
end
end
Best Practices
- •Idempotent Jobs: Jobs should be safe to run multiple times
- •Small Arguments: Pass IDs, not full objects
- •Queue Separation: Different queues for different priorities
- •Error Handling: Always handle errors gracefully
- •Logging: Log important events for debugging
- •Timeouts: Set appropriate timeouts for long-running jobs
- •Monitoring: Track job performance and failures
Integration Examples
After Create Hook
ruby
# app/models/user.rb
class User < ApplicationRecord
after_create_commit :send_welcome_email
private
def send_welcome_email
SendEmailJob.perform_later(id, "welcome")
end
end
Controller Action
ruby
# app/controllers/imports_controller.rb
class ImportsController < ApplicationController
def create
file = params[:file]
temp_path = Rails.root.join("tmp", "import_#{Time.current.to_i}.csv")
File.open(temp_path, "wb") do |f|
f.write(file.read)
end
ProcessImportJob.perform_later(temp_path.to_s)
redirect_to root_path, notice: "Import started. You'll be notified when complete."
end
end
Batch Processing
ruby
# app/jobs/batch_notification_job.rb
class BatchNotificationJob < ApplicationJob
queue_as :notifications
def perform(user_ids)
User.where(id: user_ids).find_each do |user|
SendNotificationJob.perform_later(user.id)
end
end
end
# Usage
user_ids = User.active.pluck(:id)
user_ids.each_slice(100) do |batch|
BatchNotificationJob.perform_later(batch)
end
Troubleshooting
Jobs not processing?
bash
# Check Solid Queue is running bin/rails solid_queue:start # Or in separate process bundle exec rake solid_queue:work
Failed jobs?
- •Check
/queuedashboard - •Review logs:
tail -f log/jobs.log - •Retry manually:
SolidQueue::Job.find(id).retry
Performance issues?
- •Increase worker threads in config/queue.yml
- •Split heavy jobs into smaller ones
- •Use queue priorities effectively
Examples
Checklist
- • Job class created in app/jobs/
- • Inherits from ApplicationJob
- • Queue specified with
queue_as - • Error handling added (retry_on/discard_on)
- • Enqueued from appropriate location
- • Tested with perform_now
- • Logged important events
- • Monitored in /queue dashboard