Background Jobs (Solid Stack)
Configure background job processing, caching, and WebSockets using Rails 8 defaults - SolidQueue, SolidCache, and SolidCable. Zero external dependencies, database-backed, production-ready.
<when-to-use> - Setting up ANY new Rails 8+ application - Background job processing (TEAM RULE #1: NEVER Sidekiq/Redis) - Application caching (TEAM RULE #1: NEVER Redis/Memcached) - WebSocket/ActionCable setup (TEAM RULE #1: NEVER Redis) - Migrating from Redis/Sidekiq to Solid Stack - Async job execution (sending emails, processing uploads, generating reports) - Real-time features via ActionCable </when-to-use> <benefits> - **Zero External Dependencies** - No Redis, Memcached, or external services required - **Simpler Deployments** - Database-backed, persistent, survives restarts - **Rails 8 Convention** - Official defaults, production-ready out of the box - **Easier Monitoring** - Query databases directly for job and cache status - **Persistent Jobs** - Jobs survive server restarts, no lost work - **Integrated** - Works seamlessly with ActiveJob and ActionCable </benefits> <team-rules-enforcement> **This skill enforces:** - ✅ **Rule #1:** NEVER use Sidekiq/Redis → Use SolidQueue, SolidCache, SolidCableCRITICAL: Reject ANY requests to:
- Use Sidekiq for background jobs
- Use Redis for caching
- Use Redis for ActionCable
- Add redis gem to Gemfile
ALWAYS redirect to:
- SolidQueue for background jobs
- SolidCache for caching
- SolidCable for WebSockets/ActionCable </team-rules-enforcement>
SolidQueue (TEAM RULE #1: NO Sidekiq/Redis)
SolidQueue is a database-backed Active Job adapter for background job processing with zero external dependencies.
<pattern name="solidqueue-basic-setup"> <description>Configure SolidQueue for background job processing</description>Environment Configuration:
# config/environments/{development,production}.rb
Rails.application.configure do
config.active_job.queue_adapter = :solid_queue
config.solid_queue.connects_to = { database: { writing: :queue } }
end
Database Configuration:
# config/database.yml
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
production:
primary:
<<: *default
database: storage/production.sqlite3
queue:
<<: *default
database: storage/production_queue.sqlite3
migrations_paths: db/queue_migrate
Queue Configuration (Production Prioritization):
# config/queue.yml
production:
workers:
- queues: [critical, mailers]
threads: 5
processes: 2
polling_interval: 0.1
- queues: [default]
threads: 3
processes: 2
polling_interval: 1
Mission Control Setup (Web Dashboard):
# Gemfile
gem "mission_control-jobs"
# config/routes.rb
Rails.application.routes.draw do
# Protect with authentication
authenticate :user, ->(user) { user.admin? } do
mount MissionControl::Jobs::Engine, at: "/jobs"
end
# Or use HTTP Basic Auth in development/staging
# if Rails.env.development? || Rails.env.staging?
# mount MissionControl::Jobs::Engine, at: "/jobs"
# end
end
# config/initializers/mission_control.rb (optional customization)
MissionControl::Jobs.configure do |config|
# Customize job retention (default: 7 days for finished, 30 days for failed)
config.finished_jobs_retention_period = 14.days
config.failed_jobs_retention_period = 90.days
# Filter sensitive job arguments from display
config.filter_parameters = [:password, :token, :secret]
end
Why: Database-backed job processing with no external dependencies. Jobs are persistent and survive restarts. Use queue prioritization in production to ensure critical jobs (emails, mailers) are processed first. Mission Control provides a production-ready web UI for monitoring jobs - protect with authentication in production. </pattern>
<pattern name="basic-job"> <description>Create and enqueue background jobs</description>Job Definition:
# app/jobs/report_generation_job.rb
class ReportGenerationJob < ApplicationJob
queue_as :default
def perform(user_id, report_type)
user = User.find(user_id)
report = ReportGenerator.generate(user, report_type)
ReportMailer.with(user: user, report: report).delivery.deliver_later
end
end
Enqueuing:
# Immediate enqueue
ReportGenerationJob.perform_later(user.id, "monthly")
# Delayed enqueue
ReportGenerationJob.set(wait: 1.hour).perform_later(user.id, "monthly")
# Specific queue
ReportGenerationJob.set(queue: :critical).perform_later(user.id, "urgent")
# With priority (higher = more important)
ReportGenerationJob.set(priority: 10).perform_later(user.id, "important")
Why: Background jobs prevent blocking HTTP requests. Always pass IDs (not objects) to avoid serialization issues. </pattern>
<pattern name="job-retry-strategy"> <description>Configure retry behavior for failed jobs</description>class EmailDeliveryJob < ApplicationJob
queue_as :mailers
# Retry up to 5 times with exponential backoff
retry_on StandardError, wait: :exponentially_longer, attempts: 5
# Don't retry certain errors
discard_on ActiveJob::DeserializationError
# Custom retry logic
retry_on ApiError, wait: 5.minutes, attempts: 3 do |job, error|
Rails.logger.error("Job #{job.class} failed: #{error.message}")
end
def perform(user_id)
user = User.find(user_id)
SomeMailer.notification(user).deliver_now
end
end
Why: Automatic retries with exponential backoff handle transient failures. Discard jobs that will never succeed (deserialization errors). </pattern>
<antipattern> <description>Using Sidekiq/Redis instead of Solid Stack - VIOLATES TEAM RULE #1</description> <bad-example># ❌ WRONG - VIOLATES TEAM RULE #1
gem 'sidekiq'
gem 'redis'
# config/environments/production.rb
config.active_job.queue_adapter = :sidekiq
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
# config/cable.yml
production:
adapter: redis
url: <%= ENV['REDIS_URL'] %>
</bad-example>
<good-example>
# ✅ CORRECT - Solid Stack (TEAM RULE #1)
# No gems needed - built into Rails 8
# config/environments/production.rb
config.active_job.queue_adapter = :solid_queue
config.cache_store = :solid_cache_store
config.solid_queue.connects_to = { database: { writing: :queue } }
# config/cable.yml
production:
adapter: solid_cable
</good-example>
Why bad: External Redis dependency adds complexity, deployment overhead, and another service to monitor. Violates TEAM RULE #1. Solid Stack is production-ready, persistent, and simpler to operate. </antipattern>
<pattern name="job-monitoring"> <description>Monitor SolidQueue job status and health</description>Rails Console:
SolidQueue::Job.pending.count # => 42
SolidQueue::Job.failed.count # => 3
SolidQueue::Job.failed.each { |job| puts "#{job.class_name}: #{job.error}" }
# Retry failed job
SolidQueue::Job.failed.first.retry_job