1. Why Caching Matters
Without caching, every request hits the database. A typical MySQL read takes 1–10ms. A Redis read takes 0.1–0.5ms — a 10–100x speedup. At 100,000 requests per second, the database would need to handle 100K queries/second; with a 95% cache hit rate, only 5,000 queries/second reach the DB. This is the difference between $1,000/month in database costs and $100,000/month.
2. Caching Strategies Compared
| Strategy | Read Path | Write Path | Consistency | Best For |
|---|---|---|---|---|
| Cache-Aside (Lazy Loading) | Check cache → miss → read DB → populate cache | Write DB → invalidate cache | Eventual (stale until invalidated) | General purpose, read-heavy |
| Write-Through | Always cache hit after first write | Write cache + DB synchronously | Strong (always consistent) | Read-after-write consistency needed |
| Write-Back (Write-Behind) | Same as write-through | Write cache only; async flush to DB | Eventual (risk of data loss) | Write-heavy, tolerate some loss |
| Read-Through | Cache populates itself from DB on miss | Write DB → invalidate cache | Eventual | Cache as a proxy (Redis Modules) |
| Refresh-Ahead | Pre-fetch before expiry | Write DB → update cache | Strong (proactive) | Predictable access patterns |
Cache-Aside Pattern — Code Flow
Why Delete Instead of Update on Write?
Updating the cache on write introduces a race condition: two concurrent writes could result in stale data being written to the cache after the DB update. Deletion is safe because the next read will always re-fetch from the DB. The cost is one extra cache miss per write — acceptable for most write-ratio workloads.
3. Eviction Policies
When the cache is full and a new item needs to be added, an eviction policy determines which existing item is removed.
LRU (Least Recently Used) Cache: [A (used 5s ago), B (1min ago), C (10min ago), D (1hr ago)] On new insert: evict D (accessed 1 hour ago — least recently used) Good for: temporal locality (recent = future access) LFU (Least Frequently Used) Cache: [A: 1000 hits, B: 500 hits, C: 50 hits, D: 2 hits] On new insert: evict D (accessed only 2 times — least frequent) Good for: stable popularity distribution (bestsellers stay cached) FIFO (First In, First Out) Cache: [A (inserted first), B, C, D (inserted last)] On new insert: evict A (oldest insertion, regardless of access) Simple but ignores access patterns — rarely used in production TTL-based (Time-To-Live) Every item expires automatically after N seconds Passive eviction: item removed on next access attempt after expiry Combined with LRU: expired items considered least recently used
4. Cache Stampede (Thundering Herd)
One of the most dangerous failure modes in caching. When a hot cached item expires, all threads simultaneously try to rebuild it, overwhelming the database.
Real-World Impact
A cache stampede on a single hot key can bring down a production database in seconds. In 2010, Instagram suffered an outage when a celebrity's profile cache expired — 10,000 simultaneous DB queries for the same user_id. Facebook, Twitter, and Reddit have all published post-mortems about stampedes.
Prevention Strategy 1: Mutex Lock
Prevention Strategy 2: Probabilistic Early Expiry (XFetch)
Before the TTL expires, proactively recompute with increasing probability. Items are refreshed while still "alive" in cache — no stampede window.
5. Consistent Hashing for Cache Distribution
When you have multiple cache nodes, you need to determine which node stores each key. Simple modulo hashing node = hash(key) % N means adding or removing one node remaps all keys — a full cache flush.
Consistent Hashing Ring (0 to 2^32)
0
│
Node C ● ─────────── ● Node A
│ │
│ Ring │
│ │
Node B ● ─────────────┘
Key "user:123" hashes to position 1,200,000,000
→ next clockwise node is Node A
→ if Node A is removed, key remaps only to Node B (adjacent)
→ only ~1/N keys are remapped (not all keys)
With virtual nodes (vnodes):
Each physical node gets 100–150 positions on the ring
→ even distribution despite unequal key hashing
6. Redis Cluster Architecture
Redis Cluster is the production standard for distributed caching at scale. It provides automatic sharding, replication, and failover.
- Hash slots: 16,384 total slots; CRC16(key) mod 16384 maps every key to a slot
- Node ownership: Each node owns a range of slots. A 6-node cluster: 3 primaries own slots 0–5460, 5461–10922, 10923–16383; 3 replicas mirror each primary
- Gossip protocol: Nodes communicate node health via gossip every 100ms; failure detected within 500ms
- Automatic failover: If a primary fails, its replica is elected primary within 1–2 seconds (majority vote)
- Client routing: Smart clients (redis-py-cluster, Jedis cluster) cache the slot map and route directly; dumb clients get a MOVED redirect
7. Cache Invalidation Strategies
Cache invalidation is notoriously hard. There are three main approaches:
| Strategy | Mechanism | Consistency | Complexity |
|---|---|---|---|
| TTL-based expiry | Cache auto-expires after N seconds | Eventual (up to TTL) | Very low |
| Event-driven invalidation | DB write publishes event; cache deletes key | Strong (near real-time) | Medium |
| Write-through | Write always updates both cache and DB | Strong (synchronous) | Medium |
| Cache versioning | Append version to key; old version naturally expires | Strong (new key) | Low |
| Bulk invalidation (tags) | Tag related keys; invalidate all by tag | Strong | High |
8. Monitoring Cache Health
A cache that isn't monitored will degrade silently. Track these metrics:
- Hit rate: hits / (hits + misses). Target: >90% for read-heavy systems. A drop indicates over-eviction or incorrect TTLs.
- Eviction rate: Items evicted per second. High eviction = cache too small; increase memory or reduce TTL.
- Memory usage: Track used_memory vs maxmemory. Alert at 80% to prevent OOM eviction bursts.
- Latency: Redis p99 latency should be <1ms. Spikes indicate slow commands (e.g. KEYS, unindexed SCAN).
- Connection count: Too many connections cause overhead. Use connection pooling.
How We Research and Update This Guide
We test the underlying formula or workflow, compare outputs with reliable references, and revise examples whenever the page content changes.
- The workflow or formula is tested directly in the tool and compared against independent reference examples.
- Examples are kept practical so readers can verify the result without hidden assumptions.
- Pages are revised whenever the interface, calculation flow, or surrounding guidance materially changes.
Frequently Asked Questions — Distributed Cache System Design
Cache-aside (lazy loading): the application reads from cache; on a miss, reads from DB and populates the cache. The application manages both reads and writes. Writes update the DB directly; the cache entry is either deleted (invalidation) or updated. Simple and works well when cache misses are tolerable. Write-through: every write goes to both cache and DB synchronously. Cache is always up to date. Reads never miss after first population. Higher write latency (two writes per operation). Best when read-after-write consistency is critical.
LRU (Least Recently Used) evicts the item that was accessed longest ago. Good when recency predicts future access — e.g. user session caches, recent-content feeds. LFU (Least Frequently Used) evicts the item accessed fewest times overall. Better for access patterns with stable popularity — e.g. product catalogue caches where bestsellers are repeatedly accessed. LRU is the default for most systems because it adapts quickly to changing access patterns. LFU is better when you have long-lived hot keys that should never be evicted even if not accessed for a few minutes.
Cache stampede (thundering herd) happens when a hot cached item expires and hundreds of threads simultaneously query the DB to rebuild it. The DB is overwhelmed before any thread finishes writing back. Prevention: (1) Mutex/lock: only one thread rebuilds the cache; others wait. (2) Early expiration: stochastically re-compute the cache before it expires (XFetch algorithm). (3) Background refresh: a background job proactively refreshes soon-to-expire entries. (4) Request coalescing: a single in-flight rebuild promise is shared by all waiting requests.
Consistent hashing places both nodes and keys on a virtual ring (0 to 2^32). A key is assigned to the first node clockwise on the ring from the key's hash position. When a node is added or removed, only the keys between the removed node and its predecessor are remapped — roughly 1/N of all keys, where N is the number of nodes. Traditional modulo hashing remaps ALL keys when N changes (e.g. from 3 to 4 nodes), causing a cache flush. Consistent hashing minimizes cache invalidation during topology changes.
Redis Cluster divides the key space into 16,384 hash slots. Each key is assigned to a slot via CRC16(key) mod 16384. The cluster's nodes each own a range of hash slots. A 6-node cluster (3 primary + 3 replica) might assign slots 0-5460 to node 1, 5461-10922 to node 2, and 10923-16383 to node 3. Clients learn the slot-to-node mapping from the cluster and route requests directly to the correct node — no proxy needed. On node failure, the replica promotes automatically (Raft-based election).
Avoid caching when: (1) Data changes on every read (real-time financial prices, live sensor data) — cache hit rate will be near zero; (2) Strong consistency is required — cached data may be stale; (3) Data is user-specific and rarely re-read — the cache will be populated but never hit again (cache pollution); (4) The cache access itself is the bottleneck — e.g. caching single-row DB lookups when the DB primary key read is already sub-millisecond; (5) Write-heavy workloads with few reads — cache misses on every write-invalidate cycle.