Background Jobs with Solid Queue in Rails
Every Rails app eventually needs work that should not block a web request: emails, webhook delivery, PDF generation, imports. Solid Queue - the default Active Job backend in modern Rails - keeps that work in your database instead of requiring Redis on day one.
Why Solid Queue Fits Many Apps
Sidekiq and Redis are still excellent. Solid Queue wins when you want:
- Fewer moving parts in staging and early production
- Jobs that survive with the same Postgres (or MySQL) you already run
- First-class Active Job APIs you already know (
perform_later, retries, queues)
If you already operate Redis well and need extreme throughput, Sidekiq remains a strong choice. For a large share of SaaS and internal apps, Solid Queue is enough.
Basic Setup
With Rails 8 defaults, Solid Queue is close to ready. Typical pieces:
# config/application.rb / environment
config.active_job.queue_adapter = :solid_queue
# config/queue.yml (simplified)
default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %>
polling_interval: 0.1
Run the worker process in production (Kamal, systemd, or your platform's process model) alongside Puma - jobs do not run inside the web process by default.
Design Jobs Like Public APIs
Treat each job as a small, idempotent unit of work:
class DeliverWebhookJob < ApplicationJob
queue_as :default
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
def perform(delivery_id)
delivery = WebhookDelivery.find(delivery_id)
return if delivery.completed?
Webhooks::Deliver.call(delivery)
end
end
Guidelines that save pain later:
- Pass IDs, not giant Active Record objects
- Make
performsafe to run twice (idempotent) - Use
retry_on/discard_ondeliberately - don't retry forever on bad data - Keep web requests thin: enqueue, then return
Queues and Priorities
Not all jobs are equal. Split noisy work from user-facing work:
class SendReceiptEmailJob < ApplicationJob
queue_as :mailers
end
class RebuildSearchIndexJob < ApplicationJob
queue_as :low
end
Configure workers so :mailers and :default get more threads or dedicated processes than :low. A stuck import should not delay password-reset emails.
Failures, Retries, and Visibility
Jobs fail. Plan for it:
- Retries - transient network errors deserve retries; validation errors usually do not
- Logging - structured logs with
job_id, record id, and queue name - Dashboards - Mission Control or your APM so stuck queues are obvious
- Dead letter habit - inspect discarded jobs weekly; silent discard piles grow
If a job talks to a third-party API, add timeouts and circuit-breaker style guards so one vendor outage does not fill your queue with useless retries.
When to Keep Redis / Sidekiq
Stay on Sidekiq (or similar) when you need:
- Very high job throughput with tight latency SLAs
- Advanced unique-job or batch tooling your team already depends on
- A clear ops investment already centered on Redis
You can also mix: Solid Queue for most work, a specialized backend for a hot path. Don't migrate for fashion - migrate for operational simplicity or scale.
Wrapping Up
Solid Queue gives Rails apps a durable, Active Job-native path for background work without standing up Redis on day one. Keep jobs idempotent, split queues by priority, and watch failures like production traffic.
Planning a Rails upgrade or job-backend change? Contact NekoCoding for architecture help.