Rails 8 Solid Cable: Drop Redis for WebSockets and Save Yourself a Dependency
Rails 8 shipped with Solid Cable as the default Action Cable adapter for new applications. If you’ve been running Redis solely to support WebSocket broadcasting, you can now remove that dependency entirely. Your database handles it.
Here’s how to set it up, what the performance looks like in production, and where the edges are.
What Solid Cable Actually Is
Solid Cable is a database-backed adapter for Action Cable. Instead of publishing messages through Redis pub/sub, it writes broadcast messages to a database table and polls for new messages. It ships as the solid_cable gem, included by default in Rails 8.0+.
The key difference from Redis: Solid Cable uses polling rather than pub/sub. A background thread checks the database for new messages at a configurable interval (default: 0.1 seconds). This sounds like it should be slower, and technically it is — but for the vast majority of applications, the difference is undetectable by users.
Think of it this way: if your chat message arrives in 100ms instead of 5ms, nobody notices. If removing Redis saves you a $15/month managed instance and one fewer thing to monitor at 3 AM, everyone notices.
Setting Up Solid Cable in a New Rails 8 App
New Rails 8 apps get Solid Cable configured out of the box. Run rails new myapp and check config/cable.yml:
development:
adapter: solid_cable
silence_polling: true
test:
adapter: test
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
The connects_to block points Solid Cable at a dedicated database. Rails 8 sets up a separate SQLite database for cable by default in config/database.yml:
production:
primary:
<<: *default
database: storage/production.sqlite3
cable:
<<: *default
database: storage/production_cable.sqlite3
migrations_paths: db/cable_migrate
Run the installer if it wasn’t set up automatically:
bin/rails solid_cable:install
This generates the migration for the solid_cable_messages table and updates your configuration files.
Migrating an Existing App from Redis to Solid Cable
If you’re running Rails 7 or an early Rails 8 app with Redis as your cable adapter, here’s the migration path.
Step 1: Add the gem
# Gemfile
gem "solid_cable"
bundle install
bin/rails solid_cable:install
Step 2: Run the migration
bin/rails db:migrate
The migration creates a single table:
create_table :solid_cable_messages do |t|
t.text :channel, null: false
t.text :payload, null: false, limit: 536870912
t.datetime :created_at, null: false
t.index [:channel, :created_at]
t.index :created_at
end
Step 3: Update cable.yml
Replace your Redis configuration:
# Before
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL", "redis://localhost:6379/1") %>
# After
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
Step 4: Configure the dedicated database
Add a cable database entry to config/database.yml. If you’re on PostgreSQL:
production:
primary:
<<: *default
database: myapp_production
cable:
<<: *default
database: myapp_production_cable
migrations_paths: db/cable_migrate
Step 5: Deploy and verify
After deploying your Rails 8 app, check the logs for SolidCable initialization messages. Open a browser console on a page with an active WebSocket connection and confirm messages flow through.
If everything works, you can remove the redis gem from your Gemfile (assuming nothing else uses it — check your background job setup and caching configuration first).
Performance: What to Expect
I ran Solid Cable on a production app handling ~200 concurrent WebSocket connections with roughly 50 broadcasts per second. The setup: a single PostgreSQL 15 instance on a 2-vCPU, 4GB RAM server.
Results:
- Message latency: 95th percentile at 85ms (vs ~12ms with Redis on the same hardware)
- Database load: The polling queries added about 3% CPU usage on the PostgreSQL instance
- Memory: Eliminated ~60MB of Redis memory usage
- Messages table size: With the default
message_retentionof 5 minutes, the table stayed under 2,000 rows
The latency difference is real but irrelevant for most use cases. Notification systems, live dashboards, chat features in business apps, collaborative editing cursors — all of these work perfectly fine with sub-100ms delivery.
Where it starts to matter: high-frequency trading dashboards, multiplayer game state synchronization, or any scenario where you’re broadcasting hundreds of messages per second to thousands of clients. For those, keep Redis.
Configuration Options That Matter
Solid Cable has a few knobs worth knowing about:
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 5.minutes
autotrim: true
polling_interval — How often Solid Cable checks for new messages. The default 0.1 seconds (100ms) is a good balance. Going lower increases database load. Going higher increases latency. I wouldn’t go below 0.05 seconds unless you’ve benchmarked your database and confirmed it handles the extra queries.
message_retention — How long messages stay in the table before being trimmed. The default of 5 minutes is generous. If your WebSocket connections are stable and messages are consumed quickly, you could drop this to 1 minute and keep the table even smaller.
autotrim — Automatically deletes expired messages. Leave this on. Without it, your messages table grows forever, and the polling queries slow down as the table bloats.
Using a Separate Database (and Why You Should)
Solid Cable works fine on your primary database. But using a separate database is better for two reasons:
- Isolation: Polling queries don’t compete with your application queries for connection pool slots or I/O bandwidth
- Cleanup: Trimming old messages causes writes and potential table bloat. On a separate database, this doesn’t affect your primary data.
For SQLite deployments (single-server setups), the separate database is basically free — it’s just another file. For PostgreSQL, it means running another logical database on the same server, which costs almost nothing.
If you’re running Solid Queue for background jobs too, you might wonder whether to share a database between Solid Cable and Solid Queue. Don’t. Their access patterns are different — Solid Queue does frequent row locking, Solid Cable does frequent sequential scans. Keep them separate.
The Trim Problem on Busy Systems
One thing the docs don’t emphasize enough: on high-throughput systems, the autotrim can occasionally cause lock contention. The trim operation runs a DELETE FROM solid_cable_messages WHERE created_at < ? query, which on PostgreSQL acquires row-level locks.
If you see PG::LockNotAvailable errors in your logs during high broadcast periods, you have two options:
- Run trim less frequently by increasing
message_retention - Set up a scheduled job to trim in off-peak periods:
# app/jobs/trim_cable_messages_job.rb
class TrimCableMessagesJob < ApplicationJob
queue_as :maintenance
def perform
SolidCable::Message.where("created_at < ?", 5.minutes.ago)
.in_batches(of: 1000)
.delete_all
end
end
Then disable autotrim in the config and schedule this job during quieter periods.
Multi-Server Deployments
Solid Cable works across multiple app servers as long as they all connect to the same cable database. This is the whole point of using a database adapter — shared state without a separate service.
With Redis, you needed every server to connect to the same Redis instance. With Solid Cable, you need every server to connect to the same cable database. The operational complexity is roughly equivalent, but you have one fewer service to run.
One caveat: if you’re running SQLite as your cable database, multi-server doesn’t work (SQLite doesn’t support concurrent access from multiple machines). Use PostgreSQL or MySQL for multi-server setups.
When to Keep Redis
Solid Cable isn’t the right choice everywhere. Keep Redis for Action Cable if:
- You’re broadcasting more than 500 messages per second consistently
- Sub-10ms message latency is a business requirement
- You already run Redis for caching or background jobs and the operational overhead is already paid
- You’re running a cluster of 10+ app servers (the polling load scales linearly with server count)
The break-even point in my experience: if Redis is only there for Action Cable, remove it. If Redis is serving three purposes and you’d keep it anyway, the adapter choice matters less.
FAQ
Can Solid Cable handle thousands of concurrent WebSocket connections?
Yes, but the bottleneck isn’t the adapter — it’s your app server’s ability to hold open connections. A single Puma process with 5 threads can handle around 1,000 WebSocket connections before memory becomes the constraint. Solid Cable’s polling is lightweight regardless of connection count because it queries by channel, not by connection.
Does Solid Cable work with Turbo Streams?
Solid Cable is fully compatible with Turbo Streams. Turbo Streams use Action Cable under the hood, and Solid Cable is a drop-in adapter replacement. Your turbo_stream_from helpers, broadcasts_to callbacks, and Turbo::StreamsChannel subscriptions all work unchanged.
What happens if the cable database goes down?
WebSocket connections stay open but stop receiving broadcasts. Once the database recovers, polling resumes and any messages written during the outage (from servers that could still write) get delivered if they’re within the retention window. Clients don’t disconnect — they just stop getting updates temporarily.
Should I use SQLite or PostgreSQL for the cable database?
SQLite is fine for single-server deployments and keeps things simple. PostgreSQL is necessary for multi-server setups. Performance is comparable for typical broadcast volumes (under 100 messages/second). If you’re already running PostgreSQL as your primary database, use it for cable too — one less database engine to think about.
Is Solid Cable production-ready?
It shipped as the default in Rails 8.0 and has been stable since the 1.0 release. Basecamp and HEY (37signals apps) use the Solid family of libraries in production. It’s not experimental — it’s the intended path forward for Rails applications that don’t need Redis-level throughput.
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 TouchRelated Articles
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