rails background-jobs architecture

Solid Queue vs Sidekiq vs GoodJob: Production Comparison

- 16 min read

Compare Solid Queue, Sidekiq, and GoodJob for Rails background jobs. Covers performance, cost, features, and a decision framework for production apps.

Comparing Solid Queue, Sidekiq, and GoodJob for Rails background job processing

Solid Queue is the right default for most Rails 8 apps. Choose Sidekiq when you need 5,000+ jobs/minute throughput. Choose GoodJob when you want PostgreSQL-backed jobs with lower latency and richer features than Solid Queue. All three use Active Job, so switching later is straightforward.

The Rails background job landscape shifted when Solid Queue became the default in Rails 8. But “default” doesn’t mean “best for every case.” Sidekiq remains the throughput king, and GoodJob has quietly become the most feature-rich PostgreSQL option. Each serves a different sweet spot, and picking the wrong one costs you either money, complexity, or performance.

The Comparison at a Glance

  Solid Queue Sidekiq GoodJob
Storage PostgreSQL/MySQL Redis PostgreSQL
Throughput ~800-1,200 jobs/min ~5,000-10,000+ jobs/min ~1,500-2,500 jobs/min
Job pickup latency 100ms-5s (polling) 5-15ms (push) 50-200ms (LISTEN/NOTIFY)
Rails integration Ships with Rails 8 Separate gem Separate gem
Active Job support Native Via adapter Native
Recurring jobs Built-in (recurring.yml) Requires sidekiq-cron Built-in (cron-style)
Concurrency control Built-in (limits_concurrency) Enterprise only ($) Built-in (key-based)
Batch jobs No Pro/Enterprise ($) Built-in
Dashboard Mission Control (separate gem) Sidekiq Web (included) Built-in (included)
Unique jobs Manual (DB locks) Enterprise only ($) Built-in
Extra infrastructure None Redis server None
Monthly infra cost $0 extra $50-150+ (managed Redis) $0 extra
Maturity Since 2023 Since 2012 Since 2020
Retry handling Active Job retry_on Automatic (25 retries) Active Job retry_on + auto

This table captures the headline differences. The sections below dig into what each one actually feels like in production.

Solid Queue: The Rails Default

Solid Queue is the path of least resistance for Rails 8. It ships with the framework, requires zero additional infrastructure, and covers the common cases well.

What it does well:

  • Zero-config setup in new Rails 8 apps
  • Transactional enqueue (job and data in the same DB transaction)
  • Simple recurring jobs via config/recurring.yml
  • Per-job concurrency controls with limits_concurrency
  • Free monitoring through Mission Control

Where it falls short:

  • Polling-based pickup means 100ms-5s latency
  • No batch job support
  • No built-in unique jobs
  • Younger ecosystem with fewer extensions
  • Dashboard (Mission Control) is functional but basic

I covered Solid Queue setup in detail in the practical guide, so I won’t repeat the installation here. The short version: bin/rails solid_queue:install, run migrations, configure config/solid_queue.yml, and you’re running.

Where Solid Queue Shines

Solid Queue’s killer feature is transactional enqueue. When you create a record and enqueue a job in the same request, both happen in the same database transaction:

ActiveRecord::Base.transaction do
  order = Order.create!(params)
  # This INSERT goes into the same transaction
  OrderConfirmationJob.perform_later(order.id)
end
# Both commit together, or neither does

With Sidekiq, the job enqueue goes to Redis - a separate system. If your app crashes between the database commit and the Redis write, the job is lost. With Solid Queue, that’s impossible.

For e-commerce, SaaS billing, or any workflow where “job definitely fires after data saves” matters, this is a real advantage.

Sidekiq: The Performance Champion

Sidekiq dominated Rails background jobs for over a decade, and for good reason. Redis is fast, the ecosystem is enormous, and at high volume, nothing else comes close.

What it does well:

  • 5-10x throughput compared to database-backed alternatives
  • Sub-15ms job pickup latency
  • Battle-tested at massive scale (millions of jobs/day)
  • Rich Pro/Enterprise features (batches, rate limiting, unique jobs)
  • Extensive middleware ecosystem
  • Most tutorials and Stack Overflow answers assume Sidekiq

Where it falls short:

  • Requires Redis infrastructure ($50-150+/month managed)
  • No transactional enqueue (separate datastore)
  • Advanced features locked behind paid tiers ($995-2,495/year)
  • Jobs aren’t durable by default (Redis persistence caveats)
  • One more service to monitor, back up, and scale

The Redis Question

The most common argument against Sidekiq in 2026 is the Redis dependency. Here’s when that actually matters:

Redis is a real burden when:

  • You’re a solo developer or small team managing your own infrastructure
  • You’re deploying to a single VPS with Kamal
  • You’re on a tight budget and managed Redis costs $95+/month
  • You’re already running PostgreSQL and don’t want a second datastore

Redis is not a burden when:

  • You’re on a platform that bundles Redis (Heroku, Render)
  • Your team already operates Redis for caching
  • You need Redis for other features (ActionCable, rate limiting)
  • You’re at scale where the performance justifies the cost

If Redis is already in your stack for caching or ActionCable, adding Sidekiq is nearly free in operational terms. If your only Redis use would be Sidekiq, the calculus changes.

Sidekiq’s Paid Tiers

Features you need to pay for:

Feature Sidekiq OSS (Free) Sidekiq Pro ($995/yr) Sidekiq Enterprise ($2,495/yr)
Basic job processing Yes Yes Yes
Retries with backoff Yes Yes Yes
Web dashboard Yes Yes Yes
Batch jobs No Yes Yes
Rate limiting No No Yes
Unique jobs No No Yes
Periodic jobs No No Yes
Multi-process management No No Yes
Rolling restarts No No Yes

Both Solid Queue and GoodJob include concurrency controls, recurring jobs, and unique job patterns for free. That’s worth noting when comparing total cost of ownership.

GoodJob: The PostgreSQL Powerhouse

GoodJob is the option most people overlook. Production-ready since 2020, it uses PostgreSQL like Solid Queue but picks up jobs 10-25x faster through LISTEN/NOTIFY instead of polling.

What it does well:

  • LISTEN/NOTIFY for near-real-time job pickup (50-200ms vs Solid Queue’s 100ms-5s)
  • Built-in batch support with callbacks
  • Built-in unique jobs (key-based deduplication)
  • Polished, feature-rich dashboard out of the box
  • Cron-style scheduling with a DSL
  • More mature than Solid Queue (3 years head start)
  • Active community and responsive maintainer

Where it falls short:

  • Not the Rails default (you’re opting out of the blessed path)
  • Smaller community than Sidekiq
  • No MySQL support (PostgreSQL only)
  • Slightly more configuration than Solid Queue’s zero-config
  • Less documentation and fewer tutorials than Sidekiq

The LISTEN/NOTIFY Advantage

The biggest technical difference between GoodJob and Solid Queue is how they detect new jobs.

Solid Queue polls your database at intervals:

# config/solid_queue.yml
workers:
  - queues: default
    polling_interval: 1  # Check every 1 second

GoodJob uses PostgreSQL’s LISTEN/NOTIFY:

# GoodJob listens for notifications on a PostgreSQL channel
# When a job is enqueued, the database notifies waiting workers immediately
# No polling interval - workers wake up within milliseconds

In practice, this means GoodJob picks up jobs in 50-200ms compared to Solid Queue’s 100ms-5s. For most background jobs this difference is irrelevant - your email sends just fine either way. But for jobs that trigger visible UI updates or where users are waiting for something to happen, that 2-5 second gap in Solid Queue can feel sluggish.

GoodJob Setup

# Gemfile
gem "good_job"

# Install
bin/rails good_job:install
bin/rails db:migrate
# config/application.rb
config.active_job.queue_adapter = :good_job
# config/initializers/good_job.rb
Rails.application.configure do
  config.good_job.execution_mode = :async  # Run in web process
  # Or :external for separate worker process

  config.good_job.max_threads = 5
  config.good_job.poll_interval = 30  # Fallback polling (LISTEN/NOTIFY is primary)
  config.good_job.shutdown_timeout = 25

  # Recurring jobs
  config.good_job.enable_cron = true
  config.good_job.cron = {
    daily_cleanup: {
      cron: "0 3 * * *",        # 3am daily
      class: "CleanupJob"
    },
    hourly_sync: {
      cron: "0 * * * *",        # Every hour
      class: "ExternalSyncJob",
      args: [{ full: false }]
    }
  }
end

GoodJob’s Built-in Batches

This is a feature neither Solid Queue nor free Sidekiq offers:

# Create a batch of jobs with a callback when all complete
batch = GoodJob::Batch.enqueue(on_finish: BatchCallbackJob) do
  users.each do |user|
    GenerateReportJob.perform_later(user.id)
  end
end

# BatchCallbackJob runs after ALL report jobs finish
class BatchCallbackJob < ApplicationJob
  def perform(batch, params)
    AdminMailer.all_reports_ready(batch.id).deliver_later
  end
end

With Sidekiq, you need Pro ($995/year) for this. With Solid Queue, you’d need to build it yourself with a counter and a check-and-notify pattern.

GoodJob’s Dashboard

GoodJob ships with a full dashboard - no separate gem needed:

# config/routes.rb
authenticate :user, ->(user) { user.admin? } do
  mount GoodJob::Engine, at: "/good_job"
end

The dashboard includes real-time job monitoring, cron schedule visualization, batch tracking, error inspection with full backtraces, and performance graphs. It’s more polished out of the box than Mission Control.

Performance Benchmarks

Sidekiq processes jobs 5x faster than Solid Queue and 2.5x faster than GoodJob. Here are the numbers from a standardized test - 10,000 lightweight jobs (JSON parse + DB write) on a 4-core VPS with PostgreSQL 16 and Redis 7.

Metric Solid Queue Sidekiq GoodJob
Total processing time 9.2 min 1.8 min 4.5 min
Jobs per minute ~1,090 ~5,560 ~2,220
Avg job pickup latency 1.2s 8ms 120ms
P99 job pickup latency 4.8s 45ms 380ms
Memory usage (worker) 180 MB 210 MB 195 MB
DB connections used 12 2 (Redis) + 5 (PG) 15
CPU usage (worker) 35% 55% 40%

Important caveats:

  • These are synthetic benchmarks. Your actual numbers depend on job complexity, database load, and hardware
  • Solid Queue polling was set to 1s. Lower intervals improve throughput but increase DB load
  • GoodJob used async mode with 5 threads
  • Sidekiq used 10 threads, 1 process
  • All three shared the same PostgreSQL instance

What These Numbers Mean in Practice

If your app processes 500 jobs per minute at peak, all three handle it fine. If you’re at 3,000+ jobs per minute, Sidekiq pulls ahead meaningfully. GoodJob sits in the middle - faster than Solid Queue but not touching Sidekiq’s throughput.

The latency difference matters more than raw throughput for most apps. If a user clicks “Export Report” and you enqueue a job, the difference between 8ms pickup (Sidekiq), 120ms pickup (GoodJob), and 1.2s pickup (Solid Queue) is the difference between “instant” and “noticeable pause.”

Cost Comparison

Monthly infrastructure cost for a typical SaaS application on a VPS, excluding application server costs.

  Solid Queue Sidekiq (OSS) Sidekiq Pro GoodJob
Redis (managed) $0 $95/mo $95/mo $0
Redis (self-hosted) $0 $40/mo (VPS) $40/mo (VPS) $0
License Free Free $83/mo ($995/yr) Free
Extra DB load Low None None Medium
Total (managed) $0 $95 $178 $0
Total (self-hosted) $0 $40 $123 $0

Over a year, the difference between Solid Queue/GoodJob and managed Sidekiq Pro is $2,136. That’s real money for a bootstrapped SaaS.

The hidden cost with database-backed queues is increased PostgreSQL load. With heavy job volume, you might need a larger database instance. But for most applications, the existing database handles it without issue.

Feature Matrix: What Ships Free

The free tier comparison matters because most teams start there.

Feature Solid Queue Sidekiq OSS GoodJob
Active Job native Yes Via adapter Yes
Recurring/cron jobs Yes No (need gem) Yes
Concurrency controls Yes No Yes
Unique jobs No No Yes
Batch jobs No No Yes
Job prioritization Yes (queue-based) Yes (queue weights) Yes (priority column)
Dashboard Separate gem Included Included
Transactional enqueue Yes No Yes
Multi-queue workers Yes Yes Yes
Graceful shutdown Yes Yes Yes
Separate worker process Yes Yes Yes
In-process mode Yes (Puma plugin) No Yes (async mode)

GoodJob wins the free feature comparison. Solid Queue wins on Rails integration. Sidekiq wins on raw performance and ecosystem size.

Decision Framework

After running all three in production, here’s the framework I use.

Choose Solid Queue When

  • You’re building a new Rails 8 app and want the simplest path
  • Your job volume is under 1,000 per minute
  • You value convention over configuration (the Rails way)
  • Transactional enqueue is important for data integrity
  • You’re deploying to a single VPS and want minimal infrastructure
  • Your team is small and ops simplicity is a priority

Typical fit: Early-stage SaaS, internal tools, MVPs, solo developer projects, small team apps.

Choose Sidekiq When

  • You process more than 2,000 jobs per minute consistently
  • Job pickup latency under 50ms matters for your use case
  • You need Pro/Enterprise features (batches, rate limiting, unique jobs)
  • Redis is already in your stack for caching or ActionCable
  • You’re at scale where the performance gap justifies the cost
  • Your team has experience operating Redis

Typical fit: High-traffic e-commerce, large B2B platforms, data processing pipelines, apps with real-time job requirements.

Choose GoodJob When

  • You want PostgreSQL-backed jobs but need better latency than Solid Queue
  • Batch jobs with callbacks are a core requirement
  • You need built-in unique jobs without building it yourself
  • You prefer a mature, battle-tested PostgreSQL solution
  • The polished dashboard matters for your operations team
  • You want the features of Sidekiq Pro without the cost

Typical fit: Mid-stage SaaS, apps with batch workflows (report generation, bulk operations), teams that want PostgreSQL simplicity with richer features than Solid Queue.

The Hybrid Approach

You’re not locked into one. Rails makes it easy to mix backends per job:

# Most jobs use the default (Solid Queue or GoodJob)
class ApplicationJob < ActiveJob::Base
  # Uses config.active_job.queue_adapter
end

# High-throughput jobs use Sidekiq
class EventTrackingJob < ApplicationJob
  self.queue_adapter = :sidekiq
  queue_as :firehose

  def perform(event_data)
    Analytics.track(event_data)
  end
end

# Everything else stays on the default
class WelcomeEmailJob < ApplicationJob
  queue_as :mailers

  def perform(user_id)
    UserMailer.welcome(user_id).deliver_now
  end
end

I’ve used this pattern in production: Solid Queue for 90% of jobs, Sidekiq for the high-volume analytics queue. The operational overhead of running both is modest if Redis is already in the stack.

Migration Paths

Moving Between Backends

All three support Active Job, so switching is mostly configuration:

# Switch globally
config.active_job.queue_adapter = :good_job  # or :sidekiq, :solid_queue

# Switch per-job during migration
class SomeJob < ApplicationJob
  self.queue_adapter = :good_job
end

The real migration work is in:

  1. Retry semantics - Sidekiq retries automatically; Solid Queue and GoodJob rely on Active Job’s retry_on
  2. Recurring jobs - Each backend has its own format
  3. Concurrency controls - Different APIs and mental models
  4. Monitoring - Different dashboards and metrics

I wrote a detailed Sidekiq to Solid Queue migration guide that covers the per-job rollout strategy. The same incremental approach works for any backend switch.

Trade-offs and Limitations

Solid Queue Limitations

  • Polling overhead on the database: Each poll is a query. With aggressive polling intervals and many workers, this adds load to your primary database. A separate queue database mitigates this but adds complexity
  • No LISTEN/NOTIFY: Jobs sit in the database until the next poll cycle. Minimum practical latency is 100ms, typical is 1-3 seconds
  • Young ecosystem: Fewer blog posts, tutorials, and Stack Overflow answers. When you hit an edge case, you’re reading source code
  • Missing batch support: If your workflow needs “run these 50 jobs, then do X when all finish,” you’ll build it yourself

Sidekiq Limitations

  • Redis is a single point of failure: If Redis goes down, your jobs stop. Redis persistence helps but adds operational complexity
  • No transactional enqueue: Jobs enqueued to Redis can be lost if the app crashes between the database commit and the Redis write
  • Feature gatekeeping: Concurrency controls, unique jobs, and batches require paid tiers. These are free in the PostgreSQL alternatives
  • Memory-bound scaling: Redis keeps everything in memory. Large job payloads or deep backlogs consume expensive RAM

GoodJob Limitations

  • Not the Rails default: You’re stepping off the standard path. Future Rails upgrades might favor Solid Queue’s integration patterns
  • PostgreSQL only: No MySQL support. If you’re on MySQL, GoodJob isn’t an option
  • Smaller community: Fewer contributors and users than Sidekiq means slower bug fixes for edge cases
  • LISTEN/NOTIFY scaling: Under extreme load (10,000+ notifications/second), PostgreSQL’s LISTEN/NOTIFY can become a bottleneck. At that point, you need Sidekiq anyway

When None of These Work

If you’re processing 50,000+ jobs per minute with strict ordering guarantees, look at dedicated message brokers: Kafka, RabbitMQ, or AWS SQS. These aren’t Rails job backends - they’re infrastructure-level solutions for a different class of problem.

The Bottom Line

The Rails job backend market is healthier than ever. Three strong options, each with clear strengths:

  • Solid Queue is the right default for new Rails 8 apps. Simple, free, integrated
  • Sidekiq remains unmatched at high throughput. Pay for it when you need it
  • GoodJob is the PostgreSQL option for teams that need more than Solid Queue offers

For most applications processing under 1,000 jobs per minute - which is the vast majority - the choice is between Solid Queue’s simplicity and GoodJob’s features. Sidekiq enters the conversation when you’re processing at scale or when Redis is already part of your stack.

Pick the simplest option that covers your current needs. Switching later is straightforward thanks to Active Job’s adapter abstraction.


Need help choosing or migrating your job backend? I help teams with background job architecture, performance tuning, and zero-downtime migrations. Reach out at nikita.sinenko@gmail.com.

Further Reading