← All posts

May 31, 2026

Solid Cache: when it replaces Redis and when it doesn't

Rails 8 ships a database-backed cache store. I break down where it earns its place over Redis and where Redis is still the right answer.

rails rails-8 solid-cache redis caching

Rails 8 made Solid Cache the default cache store. The pitch is honest: one less moving part in production, your cache lives in your existing database, no Redis to provision or pay for. The pitch also skips the parts where that tradeoff doesn’t work. Here’s how I think about the choice.

What Solid Cache actually is

A Rails cache store backed by a database table. You write a key, it does an INSERT … ON CONFLICT (or the equivalent for your adapter). You read a key, it does a SELECT. Eviction is by age and a configurable max-size, run by a background process. The schema is small — a solid_cache_entries table with key, value, byte_size, and an index on key.

The default config out of the box looks like this:

# rails/solid_cache · lib/generators/solid_cache/install/templates/config/cache.yml · @4e7219c
default: &default
  store_options:
    max_age: <%= 60.days.to_i %>
    max_size: <%= 256.megabytes %>
    namespace: <%= Rails.env %>

development:
  <<: *default

test:
  <<: *default

production:
  database: cache
  <<: *default

Sixty days of TTL, 256MB cap, namespaced per environment. The database: cache line points at a separate database connection — Rails 8 wants you to put cache, queue, and cable on their own databases so they don’t share write capacity with your app.

Where it shines

Read-heavy fragment caching. This is the canonical use case. You render a partial with cache @post do … end, the rendered HTML lands in solid_cache_entries, and the next request reads it back. The reads are indexed lookups — they’re fast enough for almost any web workload, and they don’t add an operational dependency.

Russian-doll caching. Same idea, nested. The outer key reads cheaply, the inner partials only re-render when their cache key changes. Solid Cache handles this with the same primitives as Redis.

Computed values that are expensive to derive but small to store. A pre-rendered chart payload, a serialized API response, a cached user count for a dashboard. Anything where the cost of recomputation dwarfs the cost of one indexed read.

Single-server or small-cluster apps. When you don’t already need Redis for something else, adding it as a cache backend is operational overhead — a process to monitor, a memory budget to tune, a connection pool to configure, a failure mode to think about. Solid Cache removes all of that.

Where Redis still wins

Atomic counters. INCR, DECR, HINCRBY. Redis was built for these and they run in microseconds. You can do it in Postgres with UPDATE … SET counter = counter + 1, and it works, but it’s a row-level lock on every increment. For rate limiting or live dashboards, Redis is the right tool.

Pub/sub. Solid Cache doesn’t do pub/sub at all — that’s not its job. If you’re using Redis as a message bus (Sidekiq, ActionCable adapter, custom subscribers), Solid Cache doesn’t replace any of that. Rails 8 has Solid Cable for ActionCable specifically, but for general-purpose pub/sub Redis is still in the picture.

Sub-millisecond latency. I haven’t run a head-to-head benchmark, but a Redis GET is generally an order of magnitude faster than a Postgres SELECT — Redis lives in RAM with a custom protocol, Postgres goes through the SQL parser, the planner, and the buffer cache. For a request that does 50 cache reads, that difference shows up. For a request that does 2, it doesn’t.

Multi-app shared cache. If three Rails apps share a cache, putting it in one of their databases means the other two now have a cross-DB dependency. Redis is designed to be shared across apps; Postgres can be, but the operational story is messier.

Eviction tuning. Redis has LRU, LFU, allkeys, volatile, multiple eviction policies you can tune per use case. Solid Cache has age-based and size-based eviction, that’s it. For most apps that’s enough. For a few it isn’t.

The real tradeoff

The honest framing isn’t “Solid Cache vs Redis” — it’s “do I want to operate Redis at all?”

If the answer is yes (you already need it for Sidekiq, ActionCable, rate limiting), then keep using it as your cache store too. The marginal cost of one more Redis use case is zero. Adding Solid Cache on top means running both, which is strictly worse than running one.

If the answer is no — small team, simple deployment, you don’t have anything else that needs Redis — Solid Cache is genuinely the right default. The DB load increase is real but small for typical web workloads, and you’ve eliminated a class of production incidents (Redis OOM, Redis split brain, Redis cache stampede when it restarts cold).

The middle case is the interesting one: you have Redis for jobs (Sidekiq) and you’re considering migrating jobs to Solid Queue too, which would let you drop Redis entirely. That’s the configuration the Rails 8 defaults are pointed at — Solid Queue, Solid Cache, Solid Cable, no Redis. It’s a legitimately attractive setup for new apps. For existing apps, the migration cost is non-trivial and the win is mostly operational simplification, not performance.

Database load

The pushback I hear most is “won’t this hammer my Postgres?” The answer depends.

For an app with one Postgres instance handling app reads, app writes, and cache reads, yes — you’ve moved load from a separate process onto your primary database. The Rails 8 conventions account for this by putting cache on its own database connection (database: cache), which on a single-server deployment is a separate database in the same Postgres instance, and on multi-server can be its own physical instance.

For a typical small-to-medium app — say, < 1000 requests per second — adding cache reads to your Postgres is unlikely to be the thing that breaks it. The cache table is small, the index is hot, and reads are simple. I haven’t measured this on a production app the way I’d want to before claiming hard numbers, but the workload pattern is friendly to Postgres’s strengths.

For a high-traffic app (thousands of req/s, cache hit rates above 95%), the math gets less obvious. At that scale you probably already have Redis for other reasons and the Solid Cache decision is moot.

What I’d actually pick

For a new Rails 8 app with no existing Redis dependency: Solid Cache, defaults, move on.

For an existing app already running Redis for other things: keep using Redis as the cache store. Don’t migrate because a default changed.

For a new app that I expect to need atomic counters or rate limiting at scale: Redis from day one, even if I could technically build it without. Knowing the right primitive is going to be there saves a migration later.

The Rails 8 defaults are good defaults. They’re not a mandate. The Solid family makes the no-Redis path viable for more apps than it used to be, and that’s a real win — but the question to ask is “what does my app actually need,” not “what’s the new default.”

  • The Rails 8 release notes for the full list of what changed.
  • solid_cache on GitHub — the source is small enough to read in an afternoon and worth it.
  • The Sidekiq vs Solid Queue vs GoodJob comparison I put up next; the cache decision often pairs with the queue decision.