Rails 8 Solid Queue, Cache and Cable explained explained
Rails 8 represents a significant shift in how we approach background jobs, caching, and real-time communication—moving away from the assumption that every Rails app needs Redis running alongside it. With Solid Queue, database-backed caching, and Action Cable without Redis, Rails 8 gives us the option to run production applications with fewer external dependencies, lower operational overhead, and simpler deployments.
For teams managing cloud infrastructure, this matters. We reduce the surface area for failure, simplify debugging, and avoid the complexity of managing another stateful service. In this guide, we'll walk through how Rails 8's database-backed defaults work, when they make sense, and how to use them in practice.
Jobs without Redis: Solid Queue
Before Rails 8, if you needed background jobs, you reached for Sidekiq or Delayed Job, both requiring either Redis or repeated database polling. Solid Queue is Rails' new default job backend, and it stores everything in your existing database.
Here's how a typical job definition works:
class SendWelcomeEmailJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
UserMailer.welcome(user).deliver_later
end
endEnqueueing the job is unchanged:
SendWelcomeEmailJob.perform_later(user.id)With Solid Queue, this job gets stored in the solid_queue_jobs table. A separate background process (the worker) polls this table, picks up jobs, executes them, and marks them complete. There's no Redis involved.
The trade-off is latency. Redis-backed systems like Sidekiq can deliver jobs to workers in milliseconds using pubsub. Solid Queue polls the database at intervals (typically every few seconds). For most workloads—sending emails, processing uploads, generating reports—this is perfectly acceptable. For systems requiring sub-second job delivery, you'd stick with Sidekiq.
In production, you'd run Solid Queue like this:
# Procfile or systemd service
worker: bundle exec rake solid_queue:startThe worker process runs continuously, executing jobs as it finds them. On Heroku, you'd add a worker dyno. On a dedicated server or Kubernetes, you'd run this as a sidecar or separate pod alongside your Rails app.
A real-world scenario: you're running an e-commerce platform on a tight budget. Your order confirmation emails, inventory updates, and analytics jobs run fine with 5-10 second latency. Solid Queue saves you a Redis instance, reducing monthly infrastructure costs and eliminating another service to monitor and scale.
Caching backed by the database
Rails' caching layer has traditionally required Memcached or Redis. Rails 8 provides a new option: database-backed caching via the Solid Cache gem.
Set it up in your environment config:
# config/environments/production.rb
config.cache_store = :solid_cache_storeThen use caching exactly as you would with any other store:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
@reviews = Rails.cache.fetch("product:#{@product.id}:reviews", expires_in: 1.hour) do
@product.reviews.includes(:user).limit(10)
end
end
endOn the first request, the cache is empty, so the block executes, fetching reviews from the database. The result gets stored in the solid_cache_entries table. Subsequent requests within one hour retrieve the cached data directly, avoiding the database query.
This is slower than in-memory Memcached but much faster than repeated database queries. For a product page getting hundreds of views per hour, the database hit from caching is negligible compared to querying the reviews table fresh every time.
Database caching works well for:
- Fragment caching: storing rendered HTML snippets for expensive partials
- Model caching: storing expensive query results
- Session storage: Rails can use the cache store for sessions, avoiding the need for a separate session backend
Again, there's a trade-off. In-memory caches are orders of magnitude faster. If your application is heavily cache-dependent and every millisecond matters, Memcached or Redis is still the right choice. But for most CRUD-heavy SaaS applications, database caching is sufficient and keeps your infrastructure simple.
Consider a CRM platform. You cache user profiles, frequently accessed deal data, and rendered sidebar partials. Database caching handles this fine. Your users don't notice the difference between 10ms and 50ms cache lookups. You eliminate another external service.
Action Cable without Redis
Action Cable is Rails' WebSocket framework for real-time features. Historically, it required Redis as a message broker to broadcast messages across multiple server instances. Rails 8 introduces the solid_cable adapter, using the database as the message broker.
Here's a typical Action Cable setup in Rails 8:
# config/cable.yml
production:
adapter: solid_cable
db_config: primaryYou define a channel:
class NotificationChannel < ApplicationCable::Channel
def subscribed
stream_from "user:#{current_user.id}:notifications"
end
def unsubscribed
# Cleanup if needed
end
endFrom anywhere in your application, you broadcast messages:
ActionCable.server.broadcast("user:#{user.id}:notifications", {
message: "Your report is ready",
data: report.to_json
})With solid_cable, each broadcast writes to the solid_cable_messages table. Connected WebSocket clients polling that table receive updates in real-time (within polling intervals, similar to Solid Queue).
This is ideal for:
- Notification systems: users see new messages or alerts without refreshing
- Collaborative editing: changes broadcast to all connected users in a document
- Live dashboards: metrics update across all viewer instances
- Status updates: job progress or order shipment tracking
The latency is slightly higher than Redis-backed Action Cable (typically 1-5 seconds for database polling vs. sub-100ms for pubsub), but acceptable for most use cases. High-frequency stock trading or multiplayer game data would still use Redis. A project management tool where team members see task updates within a few seconds works fine with solid_cable.
Practically, this means on a small Rails deployment—perhaps a single server or a few containerized instances—you have real-time communication without managing Redis sentinel, clustering, or connection pooling.
Fewer moving parts
The cumulative effect of Rails 8's database-backed defaults is operational simplicity. Consider two architectures:
| Traditional Rails stack | Rails 8 with Solid defaults |
|---|---|
| Rails app + PostgreSQL + Redis + Sidekiq + Memcached | Rails app + PostgreSQL |
| 5 moving parts, 5 failure modes | 2 moving parts, 1 database |
| Monitor Redis memory, Sidekiq queue depth, Memcached hit rate | Monitor PostgreSQL connection count and table bloat |
| Scale Redis separately; coordinate failover | Scale PostgreSQL; backup and replication handle all state |
In a startup environment or a small-to-medium SaaS, this matters enormously. Every additional service is:
- Another thing to version-upgrade and patch
- Another monitoring dashboard to check
- Another backup procedure to validate
- Another potential point of degradation during incidents
Consolidating on PostgreSQL reduces cognitive load and operational friction. Your deployment becomes simpler. Your disaster recovery procedure is already solid if your primary database is protected—no need to replicate cache or queue state separately.
This doesn't mean Redis is dead. High-performance systems, cache-heavy workloads, and latency-sensitive features still benefit from it. But Rails 8 gives us permission to start simple, defer external dependencies, and add them only when metrics show they're necessary.
Common mistakes
Assuming database caching is always fast: It's faster than a database query, but slower than in-memory. Profile your critical paths. If you're caching inside a loop, the database poll adds up. Consider moving to Memcached for frequently-accessed, expensive cache keys.
Running Solid Queue without monitoring: A stuck queue process silently stops processing jobs. Monitor the age of jobs in the queue. Set up alerts if the `solid_queue_jobs` table grows unbounded. Use tools like Kamal or systemd to auto-restart the worker if it crashes.
Not tuning database connection pools: Each Rails app instance and each Solid Queue worker needs database connections. With both consuming from the same pool, you can exhaust connections quickly. Increase `pool` size in `config/database.yml` or use PgBouncer for connection pooling.
Expecting Action Cable polling to be real-time: Don't use solid_cable for sub-second updates. If you're building a trading terminal or a fast-paced game, you need Redis. Solid Cable is for notifications, status updates, and collaborative features where a few seconds of latency is acceptable.
Key takeaways
- Rails 8's Solid Queue stores background jobs in PostgreSQL, eliminating the need for Redis and Sidekiq in many applications.
- Database-backed caching via Solid Cache trades some latency for simplicity, working well for fragment caching and moderate cache-hit scenarios.
- Action Cable with solid_cable provides real-time WebSocket communication without a Redis message broker, suitable for notifications and collaborative features.
- Consolidating on a single database reduces operational overhead, monitoring complexity, and deployment surface area for small-to-medium teams.
- These defaults are pragmatic trade-offs, not replacements. High-performance workloads still benefit from Redis, Memcached, and specialized job queues.
- Start with Rails 8's database-backed approach, then add external services when profiling or scale demands it, not as a default assumption.






Comments
Loading comments…