01

Requirements & scope

Problem Statement & Requirements

Design a distributed, in-memory caching system that sits between application servers and persistent storage to dramatically reduce read latency, offload databases, and absorb traffic spikes — similar to Redis, Memcached, or Amazon ElastiCache.

Functional Requirements

  • GET / SET / DELETE — basic key-value operations with sub-millisecond latency
  • TTL (Time-to-Live) — automatic key expiration
  • Rich data structures — strings, hashes, lists, sets, sorted sets, bitmaps, HyperLogLog, streams
  • Atomic operations — INCR, DECR, SETNX (set-if-not-exists), CAS (compare-and-swap)
  • Pub/Sub — publish-subscribe messaging for cache invalidation and real-time events
  • Lua scripting — execute atomic multi-step operations server-side
  • Persistence — optional RDB snapshots and AOF (append-only file) for durability
  • Cluster mode — automatic sharding across nodes with rebalancing
  • Replication — master-replica for high availability and read scaling

Non-Functional Requirements

  • Ultra-low latency — < 1 ms for cache hits (p99 < 2 ms)
  • High throughput — 100K-1M+ operations/second per node
  • High availability — 99.999% uptime with automatic failover
  • Horizontal scalability — add nodes to increase capacity linearly
  • Memory efficiency — maximize useful data per GB of RAM
  • Consistency — strong consistency for single-key operations; eventual consistency across replicas
  • Predictable performance — no garbage collection pauses, no swap, bounded tail latency

Out of Scope

  • Full database replacement (we're designing a cache, not a primary data store)
  • SQL query caching (application-level concern)
  • Multi-region active-active replication (mention as extension)
02

Scale estimations

Scale Estimations

Traffic

MetricValue
Read operations / second10M RPS (across cluster)
Write operations / second1M RPS (10:1 read-write ratio)
Peak reads / second30M RPS (3x average)
Number of application servers5,000
Connections per app server~20 (connection pool)
Total client connections~100,000

Storage

MetricValue
Total unique keys5 billion
Average key size50 bytes
Average value size500 bytes
Average entry (key + value + metadata overhead)~650 bytes → round to 1 KB
Total working set5B × 1 KB = ~5 TB
Hot data (20% accessed in any hour)~1 TB
Memory per node (typical)64-128 GB usable
Number of nodes for 5 TB~50-80 primary nodes
With replication (1 replica each)100-160 total nodes

Bandwidth

MetricValue
Read bandwidth (10M × 500 B avg value)~5 GB/s cluster-wide
Write bandwidth (1M × 500 B)~500 MB/s cluster-wide
Per-node bandwidth (80 nodes)~70 MB/s per node
Replication traffic~500 MB/s (async to replicas)

Hardware Estimate (Per Node)

ComponentSpec
CPU8-16 cores (Redis is mostly single-threaded per shard, but I/O threads help)
RAM128 GB (64 GB usable for data, rest for OS/fragmentation/fork overhead)
Disk500 GB NVMe SSD (for RDB snapshots + AOF persistence)
Network25 Gbps NIC
Nodes in cluster80 primary + 80 replica = 160 total
03

Layered architecture

Phase 1 of 4 · Client & edge

Start with the entry points

First, define who uses the system and where requests enter. This is the boundary of the design, not yet a complete request path.

Application
On a miss, load from the database and populate the cache with a bounded TTL.
Hash router
Use stable key routing and connection pooling to distribute load across shards.
Architecture after phase 1. Includes Application, Hash router. Application connects to Hash router.
Phase 1 — Start with Application, Hash router.SVGSVGEditable diagramEditable diagram

This first layer defines the system boundary. The next chapters add application logic and data dependencies. After examining the request paths and tradeoffs, we will add operational safeguards and bring the complete architecture together.

04

API & contracts

Phase 2 of 4 · Application & compute

Add the application logic

Keep the entry points and add the services that do the work. Give each service a clear responsibility before defining its contracts. Storage and background dependencies come next.

Cache primaries
Store hot values and apply an explicit eviction policy when memory fills.
Local L1 cache
Use short-lived in-process caching selectively to absorb extreme hot-key traffic.
Architecture after phase 2. Includes Application, Hash router, Cache primaries, Local L1 cache. Application connects to Hash router. Hash router connects to Cache primaries. Application connects to Local L1 cache.
Phase 2 — Added Cache primaries, Local L1 cache. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

API Design

Core Operations

EXAMPLE
# String operations
SET key value [EX seconds] [PX milliseconds] [NX|XX]
GET key                           → value or nil
MGET key1 key2 key3              → [val1, val2, val3]  (batch read)
MSET key1 val1 key2 val2         → OK (batch write, atomic)
INCR key                          → new_value (atomic increment)
SETNX key value                   → 1 if set, 0 if already exists (mutex primitive)

# Hash operations (object caching)
HSET user:123 name "Alice" age 30 city "Tokyo"
HGET user:123 name               → "Alice"
HGETALL user:123                 → {name: "Alice", age: "30", city: "Tokyo"}

# List operations (queue, feed)
LPUSH queue:emails msg1 msg2     → push to head
RPOP queue:emails                → pop from tail (FIFO queue)
LRANGE feed:user:123 0 19       → get first 20 items

# Set operations (unique collections)
SADD tags:article:456 "redis" "cache" "design"
SISMEMBER tags:article:456 "redis"  → 1 (true)
SINTER tags:article:456 tags:article:789  → intersection

# Sorted set operations (leaderboards, priority queues)
ZADD leaderboard 9500 "player-A" 8200 "player-B" 9800 "player-C"
ZREVRANGE leaderboard 0 9 WITHSCORES   → top 10 players
ZRANK leaderboard "player-A"            → rank

# TTL and expiration
EXPIRE key 3600                  → expires in 1 hour
TTL key                          → seconds remaining (-1 = no expiry, -2 = doesn't exist)

# Pub/Sub
PUBLISH channel:inventory msg    → broadcast to all subscribers
SUBSCRIBE channel:inventory      → receive messages

# Atomic transactions (MULTI/EXEC)
MULTI
SET account:A:balance 900
SET account:B:balance 1100
EXEC                             → atomic execution of both commands

# Lua scripting (complex atomic operations)
EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then
        return redis.call('del',KEYS[1])
      else return 0 end"
      1 lock:resource "lock-token-123"
→ atomic compare-and-delete (used for distributed locks)

Application-Level Caching Patterns (Client-Side)

python
# Cache-Aside (Lazy Loading) — most common
def get_user(user_id):
    cached = cache.get(f"user:{user_id}")
    if cached:
        return deserialize(cached)
    
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    cache.set(f"user:{user_id}", serialize(user), ex=3600)
    return user

# Write-Through
def update_user(user_id, data):
    db.update("UPDATE users SET ... WHERE id = ?", data, user_id)
    cache.set(f"user:{user_id}", serialize(data), ex=3600)

# Write-Behind (Write-Back)
def update_user_async(user_id, data):
    cache.set(f"user:{user_id}", serialize(data), ex=3600)
    queue.enqueue("db_write", {"user_id": user_id, "data": data})
    # Background worker flushes to DB in batches

# Read-Through (cache library handles miss transparently)
# Configured at infrastructure level, not application code
05

Data model

Phase 3 of 4 · Data & async work

Connect data and background work

The services now need their dependencies: durable state, fast lookups, or work that continues outside a request. Separate these responsibilities rather than treating every component as a database.

Cache replicas
Replicate cache state for availability while accepting a defined amount of staleness.
Primary database
Keep recoverable authoritative data outside the cache; cache failures must not destroy records.
Invalidation
Invalidate or update affected entries on writes and use TTL as a bound, not as perfect consistency.
Architecture after phase 3. Includes Application, Hash router, Cache primaries, Local L1 cache, Cache replicas, Primary database, Invalidation. Application connects to Hash router. Hash router connects to Cache primaries. Application connects to Local L1 cache. Cache primaries connects to Cache replicas. Application connects to Primary database. Primary database connects to Invalidation. Invalidation connects to Cache primaries.
Phase 3 — Added Cache replicas, Primary database, Invalidation. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

Data Model

Internal Memory Layout

Internal Memory Layout
Internal Memory LayoutSVGSVGEditable diagramEditable diagram

Data Structure Encodings (Memory Optimization)

Data Structure Encodings (Memory Optimization)
Data Structure Encodings (Memory Optimization)SVGSVGEditable diagramEditable diagram

Cluster Slot Assignment

Cluster Slot Assignment
Cluster Slot AssignmentSVGSVGEditable diagramEditable diagram
06

Core design decisions

Core Design Decisions

Decision 1: Single-Threaded vs Multi-Threaded Execution

This is the most debated design choice in cache systems.

Option A: Single-Threaded Command Execution (Redis)
Option A: Single-Threaded Command Execution (Redis)
Option A: Single-Threaded Command Execution (Redis)SVGSVGEditable diagramEditable diagram
ProsCons
No locks, no race conditionsCan't use multiple cores for one shard
Atomic operations are freeSingle slow command (KEYS *) blocks everything
Simple, predictable latencyMax ~300K ops/sec per shard for complex ops
Easier to reason aboutFork for persistence competes for memory
Option B: Multi-Threaded (Memcached / Dragonfly)
Option B: Multi-Threaded (Memcached / Dragonfly)
Option B: Multi-Threaded (Memcached / Dragonfly)Wide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram
ProsCons
Uses all CPU cores per nodeLock contention on hot keys
Higher single-node throughputMulti-key atomicity requires distributed locks
Better for large values (parallel serialization)Harder to reason about race conditions
Fewer nodes for same total throughputMore complex implementation
Recommendation

Single-threaded command execution (Redis model) for most use cases:

  • Simplicity and correctness trump raw throughput per node
  • Network is the bottleneck, not CPU — single thread saturates the NIC
  • Scale horizontally via sharding (more shards, more nodes)
  • I/O threads in Redis 6+ handle the socket bottleneck

Multi-threaded (Dragonfly) when:

  • You want to minimize node count (cost optimization)
  • Workload is dominated by large values (serialization-heavy)
  • You can tolerate more complex operational model

Decision 2: Eviction Policies

Decision 2: Eviction Policies
Decision 2: Eviction PoliciesSVGSVGEditable diagramEditable diagram

Decision 3: Persistence Strategy

Decision 3: Persistence Strategy
Decision 3: Persistence StrategySVGSVGEditable diagramEditable diagram

Consistent Hashing & Data Distribution

Why Consistent Hashing

Why Consistent Hashing
Why Consistent HashingSVGSVGEditable diagramEditable diagram

Redis Cluster Approach (Hash Slots)

Redis Cluster Approach (Hash Slots)
Redis Cluster Approach (Hash Slots)SVGSVGEditable diagramEditable diagram
07

Request flows

Detailed Flow Diagrams

Cache-Aside Read Flow

Cache-Aside Read Flow
Cache-Aside Read FlowWide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram

Write-Invalidate Flow (Cache + DB Consistency)

Write-Invalidate Flow (Cache + DB Consistency)
Write-Invalidate Flow (Cache + DB Consistency)Wide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram

Cluster Redirect Flow (MOVED / ASK)

Cluster Redirect Flow (MOVED / ASK)
Cluster Redirect Flow (MOVED / ASK)SVGSVGEditable diagramEditable diagram

Failover Flow (Sentinel / Cluster)

Failover Flow (Sentinel / Cluster)
Failover Flow (Sentinel / Cluster)Wide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram
08

Performance & caching

Caching Patterns Deep Dive

Pattern Comparison

Pattern Comparison
Pattern ComparisonSVGSVGEditable diagramEditable diagram

When to Use Which Pattern

PatternBest ForAvoid When
Cache-AsideGeneral-purpose caching; read-heavy workloadsWrite-heavy with strong consistency needs
Read-ThroughSame as cache-aside but with cleaner codeCustom loading logic varies per key type
Write-ThroughRead-after-write consistency requiredWrite-heavy but data rarely re-read
Write-BehindWrite-heavy, can tolerate brief data loss riskFinancial / transactional data
Refresh-AheadPredictable hot keys (config, leaderboards)Long-tail access patterns

Redis vs Memcached — When to Use Which

Redis vs Memcached — When to Use Which
Redis vs Memcached — When to Use WhichSVGSVGEditable diagramEditable diagram
09

Advanced design

A hot key can overwhelm one shard even when the cluster has plenty of free memory. Replicating reads or keeping a small local copy may help, but both add invalidation work. Measure the key’s request rate separately from total cluster traffic before assuming that adding partitions will distribute its load.

10

Edge cases

Handling Edge Cases

Cache Stampede / Thundering Herd

EXAMPLE
Problem: Hot key expires → 10,000 threads all miss simultaneously
         → all 10,000 query DB → DB overwhelmed

Solution 1: Distributed Lock (Setnx)
  Thread 1: SETNX lock:user:123 "1" EX 5 → OK (acquired)
  Thread 2: SETNX lock:user:123 "1" EX 5 → FAIL (wait)
  Thread 3: SETNX lock:user:123 "1" EX 5 → FAIL (wait)
  ...
  Thread 1: fetches from DB → SET user:123 → DEL lock:user:123
  Threads 2,3,...: retry GET → HIT

Solution 2: Probabilistic Early Expiration (best for high-throughput)
  Actual TTL = 3600s
  Each read: if current_time > (expire_time - delta * beta * ln(rand()))
    → preemptively refresh (only one thread statistically wins)
  → Cache never fully expires under load

Solution 3: Background Refresh
  TTL = 3600s, but at 3000s (80% of TTL), background thread refreshes
  → Key never expires → zero stampede
  → Requires tracking hot keys

Hot Key Problem

EXAMPLE
Problem: One key gets 100K reads/sec (celebrity profile, flash sale item)
         → Single shard/node overwhelmed

Solution 1: Local In-Process Cache (L1)
  App server caches hottest keys in process memory (HashMap with TTL)
  → Short TTL (5-10s) to limit staleness
  → Zero network overhead for hits
  → Each app server has its own copy

Solution 2: Key Replication (read replicas for hot keys)
  Detect hot key → replicate to N random shards with suffix:
    "user:celebrity" → "user:celebrity#r1", "user:celebrity#r2", ...
  Client randomly picks one → distributes load across N nodes

Solution 3: Shard-level Read Replicas
  Hot shard gets more read replicas (3 → 6)
  Client reads from any replica → load distributed

Detection: track per-key access frequency at proxy layer
  → Key exceeding 10K RPS threshold → trigger hot key mitigation

Cache Penetration

Cache Penetration
Cache PenetrationSVGSVGEditable diagramEditable diagram

Cache Avalanche

EXAMPLE
Problem: Many keys expire at the SAME time (e.g., all set at startup with same TTL)
         → Massive simultaneous cache miss → DB overload

Solution: Jittered TTL
  base_ttl = 3600
  jitter = random(0, 600)  // ±10 minutes
  actual_ttl = base_ttl + jitter
  
  → Expirations spread over 10-minute window instead of all at once

Solution: Staggered warming
  On service restart, don't load all cache at once
  → Load in batches of 1000, with 100ms delay between batches
  → Prevents overwhelming both cache and DB

Cache-Database Consistency

Cache-Database Consistency
Cache-Database ConsistencySVGSVGEditable diagramEditable diagram
11

Tradeoffs

Tradeoffs & Design Decisions Summary

DecisionOption AOption BChosenWhy
ThreadingSingle-threaded (Redis)Multi-threaded (Dragonfly)Single-threadedSimplicity, atomicity for free, network-bound anyway; scale via sharding
EvictionLRULFUallkeys-lfu (Redis 4.0+)Resists scan pollution; better hit ratio for skewed distributions
PersistenceRDB snapshotsAOF logHybrid RDB+AOFFast recovery (RDB) + minimal loss (AOF everysec)
Cluster TopologyConsistent hashing (ring)Fixed hash slots (Redis Cluster)Hash slotsSimpler, deterministic, fine-grained slot migration
Caching PatternCache-asideWrite-throughCache-asideOnly caches accessed data; cache failure = degraded, not broken
InvalidationUpdate cache on writeDelete cache on writeDeleteAvoids race conditions; idempotent; simpler
ReplicationSynchronousAsynchronousAsyncSub-ms latency; sync replication adds 1+ RTT per write
SerializationJSONMessagePack / ProtobufProtobuf3-5x smaller than JSON; faster serialization; worth the complexity
ConnectionPer-requestConnection poolPool (20-50 per app)Eliminates TCP/TLS handshake per request; reduces server socket count
Hot KeySingle shard handles allL1 local cache + key replicationL1 cache + detectionZero network for hottest keys; replicas for extreme cases
12

Reliability & fault tolerance

Phase 4 of 4 · Operations & resilience

Add operational safeguards

With the request paths and tradeoffs established, add the controls that help detect trouble and keep the system operating. The final diagram retains every layer introduced so far.

Cache protection
Coalesce concurrent misses and jitter expiry to reduce stampedes during cold starts.
Architecture after phase 4. Includes Application, Hash router, Cache primaries, Local L1 cache, Cache replicas, Primary database, Invalidation, Cache protection. Application connects to Hash router. Hash router connects to Cache primaries. Application connects to Local L1 cache. Cache primaries connects to Cache replicas. Application connects to Primary database. Primary database connects to Invalidation. Invalidation connects to Cache primaries. Cache primaries connects to Cache protection.
Phase 4 — Added Cache protection. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

Reliability & Fault Tolerance

Single Points of Failure & Mitigations

Single Points of Failure & Mitigations
Single Points of Failure & MitigationsSVGSVGEditable diagramEditable diagram

Graceful Degradation

EXAMPLE
Level 1 (single replica failure):
  → Primary continues serving. No impact.
  → New replica auto-provisioned.

Level 2 (single primary failure):
  → Sentinel promotes replica in 5-15s.
  → Brief connection errors during failover.
  → Clients reconnect to new primary via Sentinel.

Level 3 (multiple shard failures):
  → Affected key ranges unavailable.
  → Application circuit breaker activates → fall back to DB.
  → Other shards continue normally.

Level 4 (full cache cluster down):
  → ALL reads go to database.
  → Database must handle full load (capacity plan for this!).
  → Rate limiting to protect DB.
  → Cache rebuilt on restart (warm-up takes minutes to hours).

Key design principle: 
  Cache is an OPTIMIZATION, not a requirement.
  The system MUST function (slower) without cache.
  Never put data ONLY in cache unless you accept loss.

Data Loss Scenarios

Data Loss Scenarios
Data Loss ScenariosSVGSVGEditable diagramEditable diagram
13

Production architecture

High-Level Architecture

High-Level Architecture
High-Level ArchitectureSVGSVGEditable diagramEditable diagram

Component Breakdown

Component Breakdown
Component BreakdownSVGSVGEditable diagramEditable diagram

Full System Architecture (Production-Grade)

Full System Architecture (Production-Grade)
Full System Architecture (Production-Grade)Wide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram
14

Further exploration

Remove the busiest cache node during peak traffic. Estimate the new load on its neighbors and on the backing database. Compare consistent hashing, replication, and request coalescing: each protects a different part of the recovery path, and none removes the need to bound concurrent cache fills.

15

Interview playbook

Interview Tips

  1. Start with caching patterns — Explain cache-aside vs write-through vs write-behind. Most interviews expect you to know these cold. Draw the flow for cache-aside (check cache → miss → query DB → populate cache).

  2. Discuss consistency immediately — "How do you keep cache and DB in sync?" Explain why DELETE is safer than UPDATE, the double-write race condition, and CDC as the gold standard.

  3. Cache stampede is the #1 follow-up question — Have the three solutions ready: distributed lock (SETNX), probabilistic early expiration, and background refresh. Know when each applies.

  4. Know your eviction policies — Don't just say "LRU". Explain why approximate LRU (sampling) is used over true LRU (no linked list overhead), and when LFU beats LRU (scan resistance).

  5. Hot key problem — Interviewers love this. Solution: L1 in-process cache (5-10s TTL) for zero-network hot reads, then key replication across shards for extreme cases.

  6. Explain why single-threaded works — "Isn't single-threaded slow?" No — in-memory ops are 100-500 ns, network is 50K+ ns. CPU isn't the bottleneck. Single-thread gives atomicity for free. Scale horizontally via sharding.

  7. Cluster topology — Explain hash slots (16,384), how slot migration works live with MOVED/ASK redirects, and why cross-AZ replica placement matters.

  8. Persistence tradeoffs — RDB (fast recovery, data loss) vs AOF (minimal loss, slower recovery) vs hybrid (best of both). Mention fork overhead and why replicas should handle RDB saves.

  9. Size your cache — Don't guess. Calculate: key count × (key size + value size + overhead). Remember jemalloc fragmentation (~10-20%). Account for replication doubling the node count.

  10. End with operational concerns — Memory monitoring (never let it hit maxmemory without an eviction policy), slow log analysis, replication lag alerting, and the importance of connection pooling (100K app connections → 1K with pools → manageable for cache nodes).