Skip to content
ExponaBLOG
RUBY ON RAILS · DAY 029 / 100

Rails 8 Solid Queue, Cache and Cable explained explained

Rails 8's database-backed defaults.

Expona Admin6 min read

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:

ruby
class SendWelcomeEmailJob < ApplicationJob
  queue_as :default
  
  def perform(user_id)
    user = User.find(user_id)
    UserMailer.welcome(user).deliver_later
  end
end

Enqueueing the job is unchanged:

ruby
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:

ruby
# Procfile or systemd service
worker: bundle exec rake solid_queue:start

The 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:

ruby
# config/environments/production.rb
config.cache_store = :solid_cache_store

Then use caching exactly as you would with any other store:

ruby
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
end

On 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:

ruby
# config/cable.yml
production:
  adapter: solid_cable
  db_config: primary

You define a channel:

ruby
class NotificationChannel < ApplicationCable::Channel
  def subscribed
    stream_from "user:#{current_user.id}:notifications"
  end
  
  def unsubscribed
    # Cleanup if needed
  end
end

From anywhere in your application, you broadcast messages:

ruby
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.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles