35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
Background Jobs in Rails: Choosing Between Solid Queue and Sidekiq in 2026

Background Jobs in Rails: Choosing Between Solid Queue and Sidekiq in 2026

TTB Software
rails, devops
A practical guide to picking the right background job backend for your Rails app. When Solid Queue makes sense, when Sidekiq still wins, and the patterns that matter regardless of which you choose.

A client called last week with a familiar problem. Their Rails app processed PDF reports in the request cycle. Users clicked “Generate Report” and stared at a spinner for forty seconds. Sometimes the request timed out. Sometimes Heroku killed the dyno. The fix was obvious: move it to a background job. The question that followed was less obvious: which backend?

Two years ago, the answer was almost always Sidekiq. In 2026, Solid Queue has changed the conversation.

What Solid Queue Actually Is

Solid Queue shipped with Rails 8 as the default Active Job backend. It stores jobs in your existing database using FOR UPDATE SKIP LOCKED — a PostgreSQL and MySQL feature that turns a regular table into a surprisingly capable job queue.

No Redis. No extra infrastructure. Jobs live in the same database as everything else.

# That's it. This is your queue now.
class ReportJob < ApplicationJob
  queue_as :default

  def perform(report_id)
    report = Report.find(report_id)
    report.generate_pdf
    ReportMailer.ready(report).deliver_now
  end
end

If you already run PostgreSQL, Solid Queue works out of the box. You gain transactional enqueuing — the job only exists if the transaction that created it commits. No more phantom jobs pointing at records that were rolled back.

When Sidekiq Still Wins

Redis is fast. Absurdly fast for this workload. Sidekiq processes thousands of jobs per second on modest hardware. If your app enqueues millions of jobs daily, Sidekiq handles it without breaking a sweat.

Sidekiq Pro and Enterprise add features that matter at scale: rate limiting, unique jobs, batches with callbacks. A billing system that processes 50,000 invoices nightly and needs to know when the entire batch completes? Sidekiq Enterprise batches solve that cleanly.

batch = Sidekiq::Batch.new
batch.on(:success, InvoiceBatchCallback, 'run_id' => run.id)
batch.jobs do
  invoices.each { |inv| InvoiceJob.perform_async(inv.id) }
end

Solid Queue doesn’t have an equivalent. You’d build it yourself with counters and callbacks, and you’d get it wrong at least once.

The Decision Framework

Here’s how I walk clients through this:

Choose Solid Queue when:

  • You process fewer than 10,000 jobs per hour
  • You already run PostgreSQL and don’t want to operate Redis
  • Transactional enqueuing matters (e-commerce, financial apps)
  • Your team is small and operational simplicity wins
  • You’re on Rails 8 and starting fresh

Choose Sidekiq when:

  • Throughput demands are high (millions of jobs daily)
  • You need batches, rate limiting, or unique jobs
  • Redis is already in your stack for caching or pub/sub
  • Job latency under 100ms matters
  • You have the team to monitor Redis properly

The wrong reason to choose either: because a blog post told you to. Measure your actual volume. Most Rails apps process a few thousand jobs per day. At that scale, the backend barely matters.

Patterns That Matter Regardless

Whichever backend you pick, these patterns save you from 3 AM pages.

Make Jobs Idempotent

Jobs will retry. Networks fail, databases hiccup, deploys restart workers mid-execution. If running a job twice produces bad results, you have a bug.

class ChargeCustomerJob < ApplicationJob
  def perform(order_id)
    order = Order.find(order_id)
    return if order.charged?

    charge = Stripe::Charge.create(
      amount: order.total_cents,
      customer: order.stripe_customer_id,
      idempotency_key: "order-#{order.id}"
    )

    order.update!(
      charged: true,
      stripe_charge_id: charge.id
    )
  end
end

The guard clause and the Stripe idempotency key work together. Run this ten times, charge the customer once.

Use Separate Queues with Purpose

One queue is a traffic jam waiting to happen. A bulk email send with 100,000 jobs shouldn’t block a password reset email.

# config/solid_queue.yml (or sidekiq.yml equivalent)
production:
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: [critical]
      threads: 3
      processes: 1
    - queues: [default, mailers]
      threads: 5
      processes: 2
    - queues: [bulk]
      threads: 10
      processes: 1

Critical jobs get their own workers. Bulk processing can’t starve interactive work. Adjust thread counts based on whether jobs are I/O-bound or CPU-bound.

Set Timeouts

A job without a timeout is a resource leak waiting to happen. External API calls hang. Database queries go sideways. Set boundaries.

class ExternalApiJob < ApplicationJob
  limits_concurrency to: 5, key: ->(api_name) { api_name }

  def perform(api_name, payload)
    Timeout.timeout(30) do
      ExternalApi.call(api_name, payload)
    end
  rescue Timeout::Error
    Rails.logger.warn "API call to #{api_name} timed out"
    raise # Let the retry mechanism handle it
  end
end

Monitor the Right Things

Queue depth matters more than throughput. A queue that grows faster than it drains will eventually eat all your memory or disk space. Alert on queue depth, not just error rates. Pair this with structured logging so you can trace individual job failures back to the request that enqueued them.

For Solid Queue, watch the solid_queue_ready_executions table size. For Sidekiq, the dashboard shows it, or pull it from the API:

# Health check endpoint
Sidekiq::Queue.all.each do |queue|
  if queue.size > 1000
    Alerting.warn("Queue #{queue.name} depth: #{queue.size}")
  end
end

Migration Path

Already on Sidekiq and considering Solid Queue? Run them side by side. Route new, low-volume jobs to Solid Queue. Keep high-throughput jobs on Sidekiq. Active Job’s adapter-per-queue support makes this straightforward:

class LowVolumeJob < ApplicationJob
  self.queue_adapter = :solid_queue
  queue_as :default
end

class HighThroughputJob < ApplicationJob
  self.queue_adapter = :sidekiq
  queue_as :processing
end

Give it a month. Watch the metrics. Then decide if the full migration is worth it.

The Fractional CTO Take

Most background job problems aren’t backend problems. They’re design problems. Jobs that do too much. Jobs that aren’t idempotent. Jobs with no timeouts or monitoring. Fix those first. The backend choice is secondary.

If you’re starting a new Rails 8 project, use Solid Queue. One less piece of infrastructure, transactional guarantees, and enough performance for 95% of applications. If you outgrow it, Sidekiq is a well-worn upgrade path.

If you’re deploying jobs as part of a CI/CD pipeline, make sure your test suite exercises the job logic directly — don’t just test that it gets enqueued.

Don’t over-engineer the queue. Ship the feature. Watch the metrics. Adjust when reality demands it.

Frequently Asked Questions

Can Solid Queue handle scheduled or recurring jobs?

Yes. Solid Queue supports recurring tasks through its built-in scheduler. Define them in config/solid_queue.yml under the dispatchers section. For one-off scheduled jobs, use perform_later with Active Job’s set(wait:) or set(wait_until:) — Solid Queue stores the scheduled time in the database and picks them up when due.

What happens to jobs when I deploy with Solid Queue?

Solid Queue workers finish their current job before shutting down on SIGTERM. Unfinished jobs remain in the database and get picked up by new workers after the deploy. Set a reasonable timeout on your deploy process so workers aren’t killed mid-job with SIGKILL.

Should I use Active Job or Sidekiq’s native API?

Use Active Job if you want backend portability (switching between Solid Queue and Sidekiq) or if you’re on Rails 8 with Solid Queue. Use Sidekiq’s native API if you need Sidekiq-specific features like batches or rate limiting. Mixing both in the same app is fine — just be consistent within each job class.

T

About the Author

Roger Heykoop is a senior Ruby on Rails developer with 19+ years of Rails experience and 35+ years in software development. He specializes in Rails modernization, performance optimization, and AI-assisted development.

Get in Touch

Share this article

Need Expert Rails Development?

Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise

Schedule a Free Consultation