๐Ÿ’พ

Solid Cache (Rails)

SSD instead of Redis โ€” database-based Rails cache store

GitHub: rails/solid_cache

Solid Cache is a DB-based cache store created by 37signals. It starts from a simple observation: "RAM is expensive, SSD is cheap." By using SSD (several TB) instead of Redis's memory limits (several GB), you can maintain a much larger cache at much lower cost.

Please refer to the Korean version for the detailed architecture analysis, including: the core idea of storing cache entries as DB table rows, the solid_cache_entries schema, cache read/write operations, the write-frequency-proportional probabilistic expiration mechanism (EXPIRY_MULTIPLIER = 2), Maglev consistent hashing for sharding, configuration options, and the Redis vs Solid Cache comparison.

Trade-off: Slightly slower (ms level), but can operate much larger caches at much lower cost. In most Rails apps, this difference is imperceptible.

Architecture Diagram

Cache Storage Structure

๐Ÿ’พ solid_cache_entries entry.rb ↗
key string
Cache key
key_hash int64
SHA256 โ†’ signed int
value blob
Serialized value
byte_size integer
Value size (for expiry)
INDEX: key_hash (lookup) / ID order = time order (expiry)
Key point: Fast lookup with <strong>key_hash</strong> + FIFO expiry by <strong>ID order</strong> (no created_at index needed)

Cache Read/Write Flow

Write
1. Rails.cache.write('key', value)
2. Entry.write_multi โ†’ upsert_all code ↗
3. track_writes โ†’ increment counter
Probabilistic expiry trigger? expiry.rb ↗
Read
1. Rails.cache.read('key')
2. Check LocalCache (memory L1 cache)
3. Entry.read_multi โ†’ key_hash IN (?)
uncached(dirties: false)
Query cache OFF โ†’ always fresh

Expiry Mechanism (Probabilistic, not TTL)

write
write
write
write
50%!
↓ 50% of batch_size reached
expire_later (async) expiration.rb ↗
max_age
Delete if > 2 weeks
max_entries
FIFO delete by ID order
max_size
Size-based delete
EXPIRY_MULTIPLIER = 2 โ†’ 2x expiry pressure per write

Redis vs Solid Cache

Redis
Solid Cache
Storage
RAM (expensive)
SSD (cheap)
Cache size
~a few GB
~a few TB
Speed
~0.1ms
~1-5ms
Expiry
TTL-based
Probabilistic FIFO
Ops
Redis server required
DB only
Sharding
Redis Cluster
Maglev Hashing

Sharding (Maglev Consistent Hashing)

Rails.cache.write('user:123', data)
MaglevHash
CRC32('user:123') โ†’ table[2053] โ†’ shard decision
maglev_hash.rb ↗
Shard 1
cache_db_1
Shard 2
cache_db_2
Shard 3
cache_db_3
Key point: When adding/removing shards, <strong>only minimal keys are redistributed</strong> (consistent hashing)

Key Points

1

Open rails/solid_cache repository on GitHub

2

app/models/solid_cache/entry.rb โ†’ analyze cache entry model

3

app/models/solid_cache/entry/expiration.rb โ†’ analyze expiration logic

4

lib/solid_cache/store.rb โ†’ ActiveSupport::Cache::Store subclass structure

5

lib/solid_cache/store/expiry.rb โ†’ write-based probabilistic expiration trigger

6

lib/solid_cache/maglev_hash.rb โ†’ analyze consistent hashing implementation

7

lib/solid_cache/connections/ โ†’ single DB vs sharding connection management

8

db/migrate/ โ†’ check solid_cache_entries table schema

Pros

  • No Redis needed โ€” simplified operations infrastructure
  • SSD utilization โ€” much larger cache than RAM
  • ActiveSupport compatible โ€” use Rails.cache API as-is
  • Works by default in Rails 8 without separate configuration
  • Maglev hashing โ€” elegant sharding support
  • Encryption support โ€” leverages ActiveRecord Encryption

Cons

  • Slower than Redis (RAM ~0.1ms vs SSD ~1-5ms)
  • Increased DB load โ€” separate cache DB recommended
  • Probabilistic expiration โ€” no exact expiry timing like TTL
  • Redis still advantageous for high-performance real-time systems
  • Always hits DB due to query cache being disabled

Use Cases

Remove Redis: simplify infrastructure (cache with DB only) Large cache: SSD-based caching up to several TB Self-hosted apps: run without Redis in single-server apps like ONCE Rails.cache.fetch pattern: swap backend without changing existing code Sharding: distribute cache across multiple DBs for performance