
background-job
Generate background jobs for async tasks using Solid Queue. Use when user needs email sending, notif
作者 GangWooLee|オープンソース
Background Job Generator
Generate background jobs using Solid Queue for asynchronous task processing.
Quick Start
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 기본 탑재):
# Gemfile (already included)
gem "solid_queue"
# config/queue.yml (already configured)
# Solid Queue runs in-process or separate worker
Queue Dashboard:
# config/routes.rb
require "solid_queue/engine"
mount SolidQueue::Engine => "/queue"
Job Structure
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
# 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:
# 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
# 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:
# In controller or model
SendEmailJob.perform_later(user.id, "welcome")
2. Notification Job
# 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
# 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)
# 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:
# config/recurring.yml (Solid Queue)
production:
cleanup_old_posts:
class: CleanupOldPostsJob
queue: maintenance
schedule: "0 2 * * *" # 2 AM daily
Queue Priority
# 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):
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
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
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
# 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
Visit: http://localhost:3000/queue
- View pending jobs
- Monitor failed jobs
- See queue statistics
Logging
# 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
# 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
# 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
# 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
# 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?
# 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