
Rails Solid Trio or the Classic Stack? Solid Queue, Cache and Cable vs Sidekiq, Redis and AnyCable in 2026
TL;DR
Keep the Solid trio for new Rails apps on one or a few servers: Solid Queue now has batches, fiber workers and dynamic recurring tasks. Switch per component when numbers demand it: Sidekiq for sustained high job throughput, Redis for atomic counters and hot keys, AnyCable for thousands of concurrent WebSockets.
Rails 8 ships Solid Queue, Solid Cache and Solid Cable by default, but many teams still reach for Sidekiq, Redis and AnyCable. This post compares each pair with October 2026 versions and features, and gives a per-component decision table.
Also available in Português (Brasil)
Every Rails 8 app starts with the same three gems already in the Gemfile: Solid Queue for jobs, Solid Cache for Rails.cache and Solid Cable for Action Cable. All three store their data in a SQL database. And every team that has shipped Rails for more than a few years has a reflex that says: jobs go in Sidekiq, the cache goes in Redis or Memcached, and WebSockets go through Redis or AnyCable.
Both camps are right about something. This post compares each Solid component against the library it replaces, with the versions and features as they stand in October 2026, and ends with a decision table you can apply component by component.
The verdict first
For a new app on one or a few servers, keep the Solid trio. It removes a stateful service from your stack, and in 2026 the feature gaps that used to justify Sidekiq are mostly closed. Reach for the classic tools per component, when a measured need shows up: Sidekiq for sustained high job throughput or its paid features, Redis for atomic counters and sub-millisecond hot keys, AnyCable for thousands of concurrent WebSocket connections.
The important word is per component. These are three independent choices, and the best production setups often mix them.
What you are actually comparing
| Concern | Solid default | Classic choice |
|---|---|---|
| Background jobs | Solid Queue 1.7.0 (Aug 2026) | Sidekiq 8.1.7 (Aug 2026) |
Rails.cache | Solid Cache 1.0.10 (Nov 2025) | Redis or Memcached |
| Action Cable pub/sub | Solid Cable 4.1.0 (Sep 2026) | Redis adapter or AnyCable |
| Storage | Your SQL database (Postgres, MySQL, SQLite) | RAM in a separate process |
| License | MIT | Sidekiq LGPL plus commercial tiers, Redis 8 AGPLv3 or Valkey BSD |
All versions are the latest on RubyGems at the time of writing, and Rails itself is at 8.1.4.
The real trade: one fewer service versus a specialized tool
Before comparing features, be honest about what Redis costs you. It is another stateful service to provision, size, back up, monitor and fail over. It needs its memory sized for peak, not average. And if you use one Redis for both cache and jobs, you have a configuration conflict: a cache wants an eviction policy like allkeys-lru, while Sidekiq needs noeviction so it never silently drops a job. Sidekiq even logs a warning when it detects an evicting Redis. In practice that means two Redis instances, not one.
There is also licensing to keep in mind. Redis moved away from BSD in 2024, and Redis 8 came back under AGPLv3 in May 2025 alongside the source-available licenses. Valkey, the Linux Foundation fork, stays BSD and is what most cloud providers now offer. None of this blocks you, but it is one more thing to decide.
The Solid side has its own cost: every job, cache write and broadcast becomes SQL load. The default install puts each component in its own database (queue, cache, cable), which you can point at the same server or a different one. That separation is what makes it safe to start on one box and split later.
Jobs: Solid Queue vs Sidekiq
How they work
Sidekiq pushes job payloads into Redis lists and its processors fetch them with blocking reads from RAM. A job costs a few Redis operations and almost nothing else.
Solid Queue stores jobs as rows. Workers poll solid_queue_ready_executions with SELECT ... FOR UPDATE SKIP LOCKED, which lets many workers claim different rows without waiting on each other's locks. A job's lifecycle touches several tables (the job row, its ready and claimed executions, and the final update), so each job is a handful of indexed writes. Defaults are 3 threads per worker, a worker polling interval of 0.1 seconds and a dispatcher interval of 1 second. It needs MySQL 8+, MariaDB 10.6+ or PostgreSQL 9.5+ for SKIP LOCKED, and it also runs on SQLite for smaller apps.
Feature parity in 2026
The gap that used to settle this argument is mostly gone:
- Recurring jobs: built in via
config/recurring.yml, and since 1.4.0 (March 2026) you can also schedule them at runtime withSolidQueue.schedule_recurring_task. - Concurrency controls:
limits_concurrencywith a key, a limit and either:blockor:discardon conflict. Discard gives you a practical version of unique jobs. - Batches: shipped in 1.7.0 on August 21, 2026, with
on_finish,on_successandon_failurecallbacks plus progress counters. - Fiber workers: added in 1.6.0 (July 2026). A worker can run jobs as fibers on one reactor thread using Async, which suits I/O-bound work such as LLM calls.
- Dashboard: Mission Control Jobs gives you a UI to inspect, retry and discard failed jobs.
- Config validation:
bin/jobs check(1.5.0) validatesqueue.ymlandrecurring.ymlin CI or before a deploy.
Sidekiq's open-source tier has scheduled jobs, retries, the Web UI and iterable long-running jobs. Batches, enhanced reliable fetch and expiring jobs are in Pro, at $99/month or $995/year. Rate limiting, cron jobs, unique jobs, rolling restarts and encryption are in Enterprise, which starts at $269/month per 100 threads.
Here is what a sensible config/queue.yml looks like with a fiber worker for an LLM-heavy queue:
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: [ critical, default ]
threads: 5
processes: 2
polling_interval: 0.1
- queues: [ llm ]
fibers: 50
polling_interval: 0.1
Fiber workers require config.active_support.isolation_level = :fiber, which applies to the whole process. If you run Solid Queue inside Puma, your web requests get fiber isolation too, so be deliberate about it. If you want more on LLM calls from Rails, the RubyLLM guide covers the client side.
Batches look like this:
SolidQueue::Batch.enqueue(
on_success: ImportFinishedJob,
on_failure: ImportFailedJob,
description: "CSV import #{import.id}"
) do
import.rows.find_each { |row| ImportRowJob.perform_later(row) }
end
And a concurrency limit that keeps two syncs for the same account from overlapping:
class SyncAccountJob < ApplicationJob
limits_concurrency to: 1, key: ->(account) { account }, duration: 10.minutes
def perform(account)
account.sync!
end
end
The gotchas that bite in production
Retries are not automatic. Solid Queue relies entirely on Active Job. An unhandled exception becomes a failed execution and stays there until you retry or discard it. Sidekiq, by contrast, retries a failed job 25 times over roughly three weeks by default. If you move from Sidekiq, add retry_on to ApplicationJob deliberately, because Active Job's default is 5 attempts with a 3 second wait.
class ApplicationJob < ActiveJob::Base
retry_on StandardError, wait: :polynomially_longer, attempts: 10
discard_on ActiveJob::DeserializationError
end
Killed processes do not retry either. If a worker is SIGKILLed, for example by an out-of-memory killer, its in-flight jobs are marked failed with ProcessPrunedError once the heartbeat expires (5 minutes by default). retry_on cannot catch it, because the error is recorded by a different process. You recover through Mission Control or by subscribing to the fail_many_claimed.solid_queue event.
Queue naming affects performance. Exact queue names use a covering index. Wildcards like back* or paused queues force an extra DISTINCT query on every poll. Prefer [ background, backend ] over back*.
Concurrency controls are not free. Each controlled job creates semaphore and blocked-execution rows. For throttling with a limit well above 1, a dedicated queue with fewer threads is cheaper.
MySQL wants READ COMMITTED. Under the default REPEATABLE READ, InnoDB gap locks can deadlock enqueues against claims under heavy load. Set it on the queue connection only.
The Puma plugin blocks phased restarts. It requires app preloading. Fine for a single VPS, a reason to run bin/jobs as a separate role once you scale.
When Sidekiq still wins
Pick Sidekiq when job throughput is high and sustained enough that the queue database's write load becomes a real cost, when sub-10ms pickup latency matters, when you already run Redis well, or when you want Enterprise rate limiting and unique jobs without writing them. My rule of thumb: hundreds of jobs per second on a properly sized Postgres is comfortable for Solid Queue, and sustained thousands per second is where Sidekiq earns its keep. Measure your own load before trusting that line.
One more migration cost to weigh: Solid Queue only speaks Active Job. If your codebase uses include Sidekiq::Job and perform_async directly, moving means rewriting those jobs, not just swapping an adapter.
Cache: Solid Cache vs Redis or Memcached
Solid Cache flips the usual assumption. Instead of a small cache in RAM, it keeps a large cache on SSD, betting that a much higher hit rate beats a slightly slower read. A cache read from a local SSD-backed database is slower than Redis, but still far cheaper than the query or render it replaces, and a disk-sized cache rarely evicts the entry you need.
How it behaves:
- FIFO eviction, not LRU. It deletes from the oldest end of the table, which keeps size estimation cheap and avoids fragmentation.
max_agedefaults to 2 weeks, andmax_sizeormax_entriescap the size when set.- Sharding across several databases using Maglev consistent hashing.
- Encryption of values via Active Record Encryption, useful when cached fragments include personal data.
Where Redis is still the right tool: atomic counters and rate limiters (INCR with expiry), sub-millisecond hot keys read thousands of times per second, a cache shared with non-Ruby services, and cases where LRU semantics matter. Nothing stops you from using both: Solid Cache behind Rails.cache for fragments and query results, and a small Redis for the counters. The seven layers of cache post shows where each store fits in the request path.
# config/environments/production.rb
config.cache_store = :solid_cache_store
# config/initializers/redis.rb, only for counters and rate limits
REDIS = ConnectionPool.new(size: 5) { Redis.new(url: ENV.fetch("REDIS_URL")) }
Cable: Solid Cable vs Redis vs AnyCable
Solid Cable writes each broadcast to a table and polls for new messages, 0.1 seconds by default, trimming messages older than message_retention (1 day by default). Version 4.1.0 is current, and payloads can be encrypted.
The project's own benchmarks, run on a Hetzner CCX13, are the most useful numbers to look at. At 250 virtual users, Solid Cable on SQLite averaged about 146ms round trip against about 69ms for Redis. Dropping the polling interval to 0.01 seconds brought SQLite down to about 84ms. At 750 virtual users the gap widens: about 822ms on MySQL against about 163ms on Redis. For Turbo Streams on a typical app, where a few hundred users see a list update, that is perfectly fine. For a chat or a live dashboard with heavy concurrent traffic, it is not.
There are two other options worth knowing. Postgres users have Action Cable's built-in postgresql adapter, which uses LISTEN/NOTIFY and benchmarks close to Redis, but payloads are capped at 8KB. And when you need thousands of concurrent connections, delivery guarantees or presence, AnyCable moves WebSocket handling into a separate server and keeps your Rails channels. It comes in open source, Pro and managed editions.
# config/cable.yml
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 1.day
The decision table
| Your situation | Jobs | Cache | Cable |
|---|---|---|---|
| New app, one VPS, Postgres | Solid Queue | Solid Cache | Solid Cable |
| Already running Sidekiq on a healthy Redis | Keep Sidekiq | Solid Cache | Redis adapter |
| Thousands of jobs per second, low latency | Sidekiq | Either | Either |
| Need rate limits or unique jobs out of the box | Sidekiq Enterprise, or Solid Queue with :discard | Either | Either |
| I/O-bound jobs such as LLM calls | Solid Queue fibers or Sidekiq | Either | Either |
| Big cache, hit rate matters most | Either | Solid Cache | Either |
| Atomic counters, sub-millisecond reads | Either | Redis alongside | Either |
| Few hundred concurrent live users | Either | Either | Solid Cable |
| Thousands of sockets, delivery guarantees | Either | Either | AnyCable |
Moving from Sidekiq without a big bang
Solid Queue supports incremental adoption. Leave the global adapter on Sidekiq and move jobs one at a time:
class ReportJob < ApplicationJob
self.queue_adapter = :solid_queue
end
A safe order: start with low-risk jobs, add retry_on before moving anything that relied on Sidekiq's default retries, port recurring jobs from your cron gem to recurring.yml, then switch the global adapter once the Sidekiq queues drain. Keep Redis running until the retry and dead sets in the Sidekiq Web UI are empty.
Running it with Kamal
On a single server, the Rails 8 default of SOLID_QUEUE_IN_PUMA: true is enough: one container serves web and runs jobs. When jobs start competing with requests for CPU, split them into their own role:
# config/deploy.yml
servers:
web:
- 203.0.113.10
job:
hosts:
- 203.0.113.10
cmd: bin/jobs
The Kamal 2 deploy guide covers the rest of the setup, and for local development a jobs: bin/jobs line in your Procfile does the same job. The Foreman or Docker Compose post walks through both options.
Conclusion
The Solid trio is no longer the "good enough for small apps" option. With batches, fiber workers and dynamic recurring tasks in Solid Queue, a disk-sized Solid Cache and a Solid Cable that handles typical Turbo traffic, it is a reasonable default for most Rails apps in 2026. The classic stack still wins at the edges: extreme job throughput, sub-millisecond counters and large WebSocket fleets. Start Solid, watch your queue database's write load, cache hit rate and broadcast latency, and swap one component at a time when the numbers tell you to.
Sources
- rails/solid_queue github.com
- rails/solid_queue releases (1.4.0 to 1.7.0) github.com
- rails/solid_cache github.com
- rails/solid_cable (README and benchmarks) github.com
- rails/mission_control-jobs github.com
- Sidekiq: OSS, Pro and Enterprise sidekiq.org
- Sidekiq Changes.md github.com
- AnyCable documentation docs.anycable.io
- The New Stack: Redis is open source again thenewstack.io
- RubyGems: rails 8.1.4 rubygems.org
FAQ
Should a new Rails 8 app use Solid Queue or Sidekiq?
Solid Queue, unless you already know you need sustained high job throughput or Sidekiq Enterprise features. It removes Redis from the stack and, since 1.7.0, supports batches alongside concurrency controls and recurring tasks.
Does Solid Queue retry failed jobs automatically?
No. It relies on Active Job, whose retry_on default is 5 attempts with a 3 second wait. Add retry_on to ApplicationJob explicitly, especially when migrating from Sidekiq, which retries 25 times by default.
Is Solid Cache slower than Redis?
Each read is slower than RAM, but Solid Cache keeps a much larger cache on SSD, so the hit rate is usually higher. Keep Redis for atomic counters, rate limits and sub-millisecond hot keys.
When is Solid Cable not enough?
When you have many concurrent connections or latency-sensitive traffic such as chat. Its own benchmarks show round trips around 146ms at 250 users on SQLite versus about 69ms on Redis. AnyCable is the step up for thousands of sockets.
Can I mix Solid components with Sidekiq or Redis?
Yes. The choices are independent: Sidekiq for jobs with Solid Cache and Solid Cable is a common setup, and Solid Queue can be adopted one job at a time with self.queue_adapter.
Do the Solid gems need separate databases?
The default install uses separate queue, cache and cable databases, which can live on the same server. A single-database setup is supported by copying the schema into regular migrations.
Comments
Sign in with GitHub to comment.
No comments yet.