Introduction
Solid Queue is Rails 8's default background job backend. It stores jobs in the database, supports concurrency controls, priority queues, and can run embedded in Puma — eliminating the need for a separate process or Redis in most applications.
Key Concepts
- Database-Backed Queue: Jobs are persisted in database tables, surviving process restarts and deployments.
- Embedded Mode: Solid Queue can run inside the Puma web server process with
SOLID_QUEUE_IN_PUMA=true, simplifying deployment. - Concurrency Controls: Limit how many instances of a job type can run simultaneously to protect external APIs or shared resources.
Real World Context
A typical Rails 8 app deploys with zero Redis dependency — Solid Queue handles jobs, Solid Cache handles caching, and Solid Cable handles WebSockets, all backed by the database. This reduces infrastructure cost and complexity.
Deep Dive
Configuration File
yaml# config/solid_queue.yml default: &default dispatchers: - polling_interval: 1 batch_size: 500 workers: - queues: "*" threads: 5 processes: 1 polling_interval: 0.1 production: <<: *default workers: - queues: [critical, default] threads: 5 processes: 2 - queues: [low_priority] threads: 2 processes: 1
Database Setup
yaml# config/database.yml production: primary: url: <%= ENV['DATABASE_URL'] %> queue: url: <%= ENV['QUEUE_DATABASE_URL'] %> migrations_paths: db/queue_migrate
ruby# config/environments/production.rb config.active_job.queue_adapter = :solid_queue config.solid_queue.connects_to = { database: { writing: :queue } }
Embedded Mode (Run in Puma)
yaml# config/deploy.yml (Kamal) env: clear: SOLID_QUEUE_IN_PUMA: true
This starts Solid Queue automatically when Puma boots — no separate worker process needed.
Concurrency Controls
rubyclass SyncToExternalApiJob < ApplicationJob limits_concurrency to: 2, key: ->(*args) { "api_sync" } def perform(record_id) ExternalApi.sync(Record.find(record_id)) end end
Priority Queues
rubyclass CriticalAlertJob < ApplicationJob queue_as :critical end class ReportGenerationJob < ApplicationJob queue_as :low_priority end
Common Pitfalls
- Not setting up a separate queue database — For high-volume apps, job tables should use a separate database to avoid lock contention with your application data.
- Using embedded mode for heavy workloads — Embedded mode is convenient for simple apps, but CPU-intensive jobs can starve web request threads.
Best Practices
- Start with embedded mode — For most apps,
SOLID_QUEUE_IN_PUMA=trueis the simplest deployment. Scale to separate workers when needed. - Use concurrency controls for external APIs — Prevent rate limiting by capping concurrent jobs that call the same external service.
Summary
- Solid Queue stores jobs in the database — no Redis required.
- Embedded mode (
SOLID_QUEUE_IN_PUMA=true) runs jobs inside Puma. - Use a separate queue database for high-volume production apps.
- Concurrency controls prevent overwhelming external APIs.
- Configure priority queues to process critical jobs first.
Code Examples
ruby
# Limit concurrent API calls to prevent rate limiting
class ExternalSyncJob < ApplicationJob
limits_concurrency to: 3, key: ->(*args) { "external_api" }
queue_as :default
def perform(user_id)
user = User.find(user_id)
ExternalCrm.sync_user(user)
end
end
# Only 3 instances of this job run simultaneously