Skip to content
advancedPhase 52 · HLD Case Studies

Distributed Cache (HLD)

Design a distributed caching system like Memcached.

1h 30m
0 problems
Topic Progress0%

Requirements & Architecture

Functional Requirements

Requirement Description
GET/SET/DELETE Basic key-value operations with sub-millisecond latency
TTL (Time-to-Live) Keys expire automatically after a configurable duration
Pub-Sub Publish/subscribe messaging for real-time event notifications
Transactions Multi-key atomic operations (EXEC, MULTI, WATCH)
Data Structures Rich types: strings, hashes, lists, sets, sorted sets, streams
Batch Operations Pipeline multiple commands in a single round-trip
Keyspace Notifications Notify clients when keys are created, expired, or deleted

Non-Functional Requirements

Requirement Target Rationale
Sub-millisecond Latency p99 < 1ms Cache must be faster than the database it accelerates
High Availability 99.99% uptime Cache outage should not cascade to dependent services
Horizontal Scaling Linear throughput with nodes Must handle 1M+ ops/sec across cluster
Fault Tolerance Survive N-1 node failures per shard No data loss on single node failure
Data Durability Optional, configurable Some use cases need persistence, others don't
Memory Efficiency Minimal overhead per key Maximize useful data per GB of RAM

Core Architecture

┌──────────────────────────────────────────────────────────┐
│                    Client Applications                     │
└──────────────┬───────────────────┬──────────────────────┘
               │                   │
       ┌───────▼───────┐   ┌──────▼───────┐
       │  Client SDK   │   │  Client SDK  │
       │  (Connection  │   │  (Connection │
       │   Pool, Hash  │   │   Pool)      │
       │   Routing)    │   │              │
       └───────┬───────┘   └──────┬───────┘
               │                  │
               ▼                  ▼
┌──────────────────────────────────────────────────────────┐
│              Cache Cluster (N nodes)                      │
│                                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ Shard 0  │  │ Shard 1  │  │ Shard 2  │   ...        │
│  │ ┌──────┐ │  │ ┌──────┐ │  │ ┌──────┐ │              │
│  │ │Primary│ │  │ │Primary│ │  │ │Primary│ │              │
│  │ └──┬───┘ │  │ └──┬───┘ │  │ └──┬───┘ │              │
│  │    │     │  │    │     │  │    │     │              │
│  │ ┌──▼───┐ │  │ ┌──▼───┐ │  │ ┌──▼───┐ │              │
│  │ │Replica│ │  │ │Replica│ │  │ │Replica│ │              │
│  │ └──────┘ │  │ └──────┘ │  │ └──────┘ │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│                                                          │
│  ┌──────────────────────────────────────┐              │
│  │    Cluster Manager (Sentinel/Raft)   │              │
│  │    - Failure detection                │              │
│  │    - Automatic failover               │              │
│  │    - Slot rebalancing                 │              │
│  └──────────────────────────────────────┘              │
└──────────────────────────────────────────────────────────┘
               │
       ┌───────▼───────┐
       │ Persistence   │
       │ Layer (RDB/   │
       │ AOF/Hybrid)   │
       └───────────────┘

Component Responsibilities

Component Role
Client SDK Connection pooling, consistent hash routing to correct shard, pipelining, retry logic
Cache Nodes Store data, handle read/write commands, manage local memory
Shard A group of primary + replica nodes holding a partition of the total dataset
Cluster Manager Detects node failures, triggers failover, rebalances slots across shards
Persistence Layer Optional RDB snapshots and/or AOF for durability across restarts

Technology Choices

Component Option A Option B Trade-off
Cache Engine Redis Memcached Redis: richer data structures, persistence. Memcached: simpler, multi-threaded
Partitioning Redis Cluster (16384 slots) Client-side consistent hashing Cluster: managed by server. Client-side: simpler but manual
Replication Async primary-replica Sync (WAIT command) Async: faster. Sync: stronger consistency
Failover Redis Sentinel Raft-based (etcd) Sentinel: Redis-native. Raft: more robust consensus
Persistence RDB + AOF hybrid RDB only Hybrid: best durability. RDB: simpler, faster restarts

Key Design Decisions:

  1. Redis over Memcached: Redis provides richer data structures (sorted sets, hashes, streams), persistence, pub-sub, and built-in clustering. Memcached is simpler but lacks these features.
  2. Async replication: Strong consistency (WAIT command) doubles latency. Most cache use cases tolerate eventual consistency.
  3. 16384 slots: Redis Cluster uses 16384 hash slots for partitioning. This number is a balance between granularity and memory overhead.
  4. Hybrid persistence: RDB snapshots for fast restarts + AOF for durability. Configurable per use case.

Data Partitioning & Replication

Consistent Hashing (Traditional)

       Hash Ring (0 to 2^32 - 1)

              Node A (hash=0x1000)
                  ╱    \
                ╱        \
              ╱            \
    Node D ──              ── Node B
   (0xF000)                  (0x4000)
              ╲            ╱
                ╲        ╱
                  ╲    ╱
              Node C (0x8000)

Key "user:123" → hash = 0x3000 → maps to Node B (next clockwise)
Key "order:456" → hash = 0x9000 → maps to Node C

Problem: Simple consistent hashing leads to uneven distribution. Virtual nodes solve this:

Each physical node gets V virtual nodes (e.g., V = 150):

Node A: hash("A-0"), hash("A-1"), ..., hash("A-149")
Node B: hash("B-0"), hash("B-1"), ..., hash("B-149")
Node C: hash("C-0"), hash("C-1"), ..., hash("C-149")

Result: ~均匀 distribution across all nodes

Redis Cluster Slot-Based Partitioning

Redis Cluster uses a fixed number of 16384 hash slots for more predictable distribution:

Slot Calculation:
  slot = CRC16(key) mod 16384

Example:
  CRC16("user:123") = 5498 → slot 5498
  CRC16("order:456") = 12003 → slot 12003

Slot Distribution Across Shards:
  Shard 0: slots 0 - 5460      (5461 slots)
  Shard 1: slots 5461 - 10922  (5462 slots)
  Shard 2: slots 10923 - 16383 (5461 slots)

Why 16384 slots?

  • Enough granularity for balanced distribution across up to 1000 nodes
  • Small enough for efficient slot migration (each node broadcasts its slot map)
  • Power of 2 minus something: allows efficient bitmask operations

Hash Tagging for Multi-Key Operations

When you need atomic multi-key operations, use hash tags to force keys to the same slot:

Without hash tag:
  SET user:123 → slot 5498 (Shard 0)
  SET user:123:profile → slot 8201 (Shard 2)  ← Different shard!
  MULTI/EXEC won't work across shards

With hash tag:
  SET {user:123}:name → slot 5498 (Shard 0)
  SET {user:123}:profile → slot 5498 (Shard 0)  ← Same shard!
  MULTI/EXEC works atomically

Replication Architecture

Shard 0:
┌─────────────────┐     ┌─────────────────┐
│   Primary Node   │────▶│  Replica Node    │
│   (Slots 0-5460)│     │  (Read-only)     │
│                  │◀────│                  │
│  - All writes    │     │  - Receives RDB  │
│  - Read ops      │     │    snapshots     │
│  - Propagates    │     │  - AOF replay    │
│    to replicas   │     │  - Can serve     │
│                  │     │    read replicas │
└─────────────────┘     └─────────────────┘
        │
        │ (on failure)
        ▼
┌─────────────────┐
│  Sentinel/Raft   │
│  promotes replica│
│  to primary      │
└─────────────────┘

Async Replication Flow

Client WRITE to Primary:
  1. Primary receives SET key value
  2. Primary writes to local memory
  3. Primary returns OK to client (doesn't wait for replicas)
  4. Primary asynchronously propagates write to replicas
  5. Replica receives and applies write

Timeline:
  t=0ms:  Client sends SET to Primary
  t=0.1ms: Primary applies write, returns OK
  t=0.2ms: Replica receives replication stream
  t=0.3ms: Replica applies write
  
  Between t=0.1ms and t=0.3ms: replica is stale

Strong Consistency with WAIT

Client can request synchronous confirmation:
  SET key value
  WAIT 1 500  # Wait for 1 replica to confirm, timeout 500ms

Timeline:
  t=0ms:    Client sends SET to Primary
  t=0.1ms:  Primary applies write
  t=0.1ms:  Primary propagates to replica
  t=0.3ms:  Replica confirms
  t=0.3ms:  Primary returns OK to client
  
  Trade-off: +0.2ms latency for strong consistency

Failover Mechanism

Failure Detection (Gossip Protocol)

Every node periodically:
  1. Sends PING to random subset of nodes
  2. Expects PONG within cluster-node-timeout (e.g., 15 seconds)
  3. If no PONG: mark node as PFAIL (possible failure)
  4. If majority agrees node is PFAIL: mark as FAIL
  5. If primary is FAIL: initiate failover

Gossip Protocol:
  - Each node shares its view of cluster state with random peers
  - Information spreads: O(log N) rounds to reach all nodes
  - No single point of failure for detection

Automatic Failover Sequence

1. Primary P0 fails (detected by gossip)
2. Replica R0 is elected as new primary by sentinel/quorum
3. R0 promotes itself: SLAVEOF NO ONE
4. R0 takes ownership of P0's slots
5. Other nodes update their slot maps
6. Clients receive MOVED redirect to new primary
7. Old P0's data (if it comes back) is discarded or re-synced as replica

Failover time: typically 5-15 seconds

Slot Migration (Rebalancing)

When adding/removing nodes, slots must be migrated:

Before: Shard 0 (slots 0-5460), Shard 1 (slots 5461-10922)
After adding Shard 2:

1. Shard 1 enters IMPORTING state for new slots
2. Shard 0 enters MIGRATING state for slots being moved
3. Migrate keys one by one using MIGRATE command
4. Update slot ownership across cluster
5. Clear MIGRATING/IMPORTING states

Migration is online - reads/writes continue during migration

Data Model (Internal)

# Slot ownership map (each node maintains this)
node_0 slots: 0-5460
node_1 slots: 5461-10922
node_2 slots: 10923-16383

# Replication info
node_0 replicas: [node_3]
node_1 replicas: [node_4]
node_2 replicas: [node_5]

# Gossip state (shared between nodes)
{
  "node_id": "abc123",
  "ip": "10.0.0.1",
  "port": 6379,
  "slots": "0-5460",
  "replicas": ["def456"],
  "epoch": 42,
  "state": "ok"
}

Consistency & Eviction

Cache Patterns

Cache-Aside (Lazy Loading)

Most common pattern. Application manages cache explicitly.

Read:
  1. App checks cache for key
  2. Cache HIT → return cached value
  3. Cache MISS → query database
  4. Store result in cache with TTL
  5. Return value

Write:
  1. App writes to database
  2. App invalidates (deletes) cache key
  3. Next read repopulates cache

Pros: Simple, only caches what's requested, handles cold starts
Cons: Cache miss = 3 network calls (cache + db + cache write)
App ──▶ Cache (check) ──miss──▶ Database (query) ──▶ Cache (store) ──▶ Return
         │
         └──hit──▶ Return cached value

Read-Through

Cache itself manages loading from database.

Read:
  1. App requests key from cache
  2. Cache MISS → cache loads from DB automatically
  3. Cache stores result with TTL
  4. Returns value to app

Pros: App code is simpler (just reads cache)
Cons: Cache must know about DB, harder to customize loading logic

Write-Through

All writes go through cache to database synchronously.

Write:
  1. App writes to cache
  2. Cache synchronously writes to database
  3. Returns success to app

Pros: Cache and DB always consistent
Cons: Write latency = cache + DB latency, write amplification

Write-Behind (Write-Back)

Writes go to cache first, DB updated asynchronously.

Write:
  1. App writes to cache
  2. Cache returns success immediately
  3. Cache asynchronously batches writes to DB

Pros: Very fast writes, batch DB updates
Cons: Risk of data loss if cache crashes before DB sync
Use cases: Session data, analytics counters, non-critical writes

Pattern Comparison

Pattern Read Latency Write Latency Consistency Data Loss Risk Complexity
Cache-Aside Miss: 3 RTTs Write + invalidate Eventual Low Low
Read-Through Miss: 2 RTTs Write + invalidate Eventual Low Medium
Write-Through Read: 1 RTT Write: 2 RTTs Strong None Medium
Write-Behind Read: 1 RTT Write: 1 RTT Eventual High High

Eviction Policies

When memory is full, the cache must evict keys to make room:

Policy Description Best For Memory Overhead
LRU Least Recently Used - evict key accessed longest ago General purpose, temporal locality Low (1 bit per key)
LFU Least Frequently Used - evict least accessed key Frequency-based workloads Medium (counter per key)
TTL-based Evict keys that have expired Time-sensitive data None (natural expiry)
Random Evict random key Uniform access patterns None
No-eviction Reject writes when full (return OOM) When data must not be lost None
Allkeys-LRU LRU across all keys (not just expiring) Mixed TTL workloads Low
Volatile-LRU LRU only among keys with TTL set Protect permanent keys Low
Redis eviction configuration:
  maxmemory 4gb
  maxmemory-policy allkeys-lru

Eviction sample size:
  Redis checks 5 random keys, evicts the one with oldest access time
  (approximation of true LRU, much cheaper than tracking full access order)

LRU vs LFU Decision

Workload Analysis:

  If access pattern has TEMPORAL LOCALITY (recently accessed = likely accessed again):
    → Use LRU
    Example: User session data, recently viewed products

  If access pattern has FREQUENCY LOCALITY (frequently accessed = likely accessed again):
    → Use LFU
    Example: Popular product catalog, reference data

  If access pattern is UNIFORM (all keys equally likely):
    → Use Random or FIFO
    Example: Rate limiting counters, temporary tokens

Consistency Guarantees

Eventual Consistency (Default)

Replica lag timeline:
  t=0ms:   Primary: SET user:123 = {name: "Alice"}
  t=0.1ms: Primary returns OK to client
  t=0.2ms: Replica receives update
  t=0.3ms: Replica applies update
  
  Between t=0.1ms and t=0.3ms:
    Read from primary: {name: "Alice"}  ← correct
    Read from replica: {name: "Bob"}    ← stale (old value)

For most cache use cases: acceptable
  - Stale data is temporary
  - Cache miss falls back to DB (source of truth)

Strong Consistency with WAIT

When consistency matters:
  SET session:abc {data} 
  WAIT 1 500  # Block until 1 replica confirms
  
  Now reading from any node returns {data}
  
  Trade-off:
    Without WAIT: 0.1ms write latency
    With WAIT:    0.3ms write latency
    
  Use for: Financial data, inventory counts, leaderboards

Consistency Patterns

Pattern Implementation Use Case
Read-after-write Read from primary after write User profile updates
Session stickiness Route same user to same primary Session data
Lease-based Writer holds lease, others read stale Leader election
Version vectors Track vector clocks per key Conflict resolution

Persistence Options

RDB Snapshots

Mechanism: Fork child process, write entire dataset to disk

Schedule: save 900 1      # After 900s if 1 key changed
          save 300 10     # After 300s if 10 keys changed
          save 60 10000   # After 60s if 10K keys changed

Pros:
  - Compact file, fast restart
  - Minimal performance impact during snapshot (copy-on-write)
  - Good for backups

Cons:
  - Data loss between snapshots (up to last snapshot)
  - Fork can cause latency spike with large datasets
  - Large datasets take time to snapshot

AOF (Append-Only File)

Mechanism: Log every write command to file

fsync policy:
  appendfsync always   # fsync after every write (safest, slowest)
  appendfsync everysec # fsync once per second (balanced)
  appendfsync no       # let OS decide (fastest, riskiest)

AOF Rewrite:
  - Periodically rewrite AOF to remove redundant commands
  - E.g., SET key1, SET key1, SET key1 → only keep final SET key1
  - Reduces file size, speeds up restart

Pros:
  - Better durability than RDB
  - Configurable durability level
  - Human-readable log

Cons:
  - Larger files than RDB
  - Slower restart (must replay commands)

Hybrid Persistence (Redis 4.0+)

Combine RDB + AOF:
  1. RDB snapshot provides fast restart baseline
  2. AOF captures writes since last RDB snapshot
  3. On restart: load RDB, then replay AOF

Recovery time: RDB load (fast) + AOF replay (incremental)
Durability: Only lose data from last AOF fsync (typically 1 second)

Memory Management

Redis Memory Architecture:

  ┌─────────────────────────────────────┐
  │            Redis Process             │
  │                                      │
  │  ┌─────────────────────────────┐    │
  │  │      jemalloc allocator     │    │
  │  │  ┌─────┐ ┌─────┐ ┌─────┐  │    │
  │  │  │Key1 │ │Key2 │ │Key3 │  │    │
  │  │  └─────┘ └─────┘ └─────┘  │    │
  │  │  ┌─────┐ ┌─────┐         │    │
  │  │  │Key4 │ │Key5 │         │    │
  │  │  └─────┘ └─────┘         │    │
  │  └─────────────────────────────┘    │
  │                                      │
  │  maxmemory: 4GB                      │
  │  used_memory: 3.2GB (80%)            │
  │  mem_fragmentation_ratio: 1.1        │
  └─────────────────────────────────────┘
Metric Description Healthy Range
used_memory Actual data + overhead < maxmemory
mem_fragmentation_ratio RSS / used_memory 1.0 - 1.5
mem_allocator Memory allocator used jemalloc (default)
maxmemory_policy Eviction policy Based on workload

Fragmentation:

  • Ratio > 1.5: memory is fragmented, consider restarting with RDB restore
  • Ratio < 1.0: using swap, critical performance issue
  • jemalloc reduces fragmentation vs glibc malloc

Client-Side Design

Connection Pooling

Client maintains pool of connections to each node:

  connections_per_node = 5
  total_connections = nodes × connections_per_node
  
  Connection lifecycle:
    1. Create connections on client startup
    2. Reuse connections across requests (no TCP overhead)
    3. Health check idle connections every 30s
    4. Replace failed connections automatically

Benefits:
  - Avoid TCP handshake overhead (~1ms per connection)
  - Limit concurrent connections per node
  - Automatic failover to healthy connections

Pipeline & Batch Operations

Without pipeline (3 round-trips):
  Client → SET key1 ──▶ Node
  Client ◀── OK ────── Node
  Client → SET key2 ──▶ Node
  Client ◀── OK ────── Node
  Client → SET key3 ──▶ Node
  Client ◀── OK ────── Node
  Total: 3 RTTs ≈ 3ms

With pipeline (1 round-trip):
  Client → [SET key1, SET key2, SET key3] ──▶ Node
  Client ◀── [OK, OK, OK] ─────────────────── Node
  Total: 1 RTT ≈ 1ms

MGET for batch reads:
  MGET key1 key2 key3 → [val1, val2, val3]
  Single RTT instead of 3

Client-Side Retry Strategy

Retry flow:
  1. Send command to target node
  2. If MOVED error → update slot map, retry to new node
  3. If ASK error → retry to specified node with ASKING flag
  4. If connection error → retry to next node in pool (up to 3 times)
  5. If all retries fail → return error to application

Timeout settings:
  - Connect timeout: 200ms
  - Read timeout: 500ms
  - Write timeout: 500ms

Complete Request Flow

1. Client: GET user:123
   │
   ▼
2. Client SDK computes slot: CRC16("user:123") mod 16384 = 5498
   │
   ▼
3. Client SDK looks up slot map → Shard 1 (Primary)
   │
   ▼
4. Client sends GET to Shard 1 Primary
   │
   ├── HIT: Return value directly
   │
   └── MISS:
       │
       ├── App queries database
       │
       ├── App stores in cache: SET user:123 {data} EX 3600
       │
       │   (Redis:
       │     1. Compute slot for key
       │     2. Route to Shard 1 Primary
       │     3. Store in memory with TTL
       │     4. Async replicate to replica
       │     5. Return OK)
       │
       └── Return value to app

This design provides sub-millisecond latency, linear horizontal scaling, and fault tolerance suitable for Amazon-scale caching workloads.

Practice Problems

0/3solved
Design Distributed Cache (Design Redis Cluster) System

Design a scalable Distributed Cache (Design Redis Cluster) system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Distributed Cache (Design Redis Cluster) Scaling

How would you scale Distributed Cache (Design Redis Cluster) to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Distributed Cache (Design Redis Cluster) Failure Modes

Analyze potential failure modes for Distributed Cache (Design Redis Cluster) and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. Why does Redis Cluster use 16384 hash slots instead of consistent hashing?

Question 1 options

2. What is the primary trade-off of the cache-aside pattern?

Question 2 options

3. What happens when a Redis primary node fails in a cluster?

Question 3 options

4. When should you use LFU instead of LRU eviction?

Question 4 options

5. What is the mem_fragmentation_ratio in Redis and what is a healthy range?

Question 5 options

Flashcards

Question

What is consistent hashing and why is it used in distributed caches?

Answer

A hashing technique where both keys and nodes are mapped to a hash ring. A key is assigned to the next node clockwise on the ring. When nodes are added/removed, only a fraction of keys need to be remapped (1/N), minimizing redistribution. Virtual nodes improve distribution uniformity.

Question

What are the 16384 hash slots in Redis Cluster?

Answer

Redis Cluster divides the keyspace into 16384 fixed slots. Each key maps to a slot via CRC16(key) mod 16384. Each shard owns a range of slots. This enables deterministic routing, efficient slot migration, and rebalancing when nodes are added/removed.

Question

What is the difference between RDB and AOF persistence in Redis?

Answer

RDB: periodic fork-based snapshots (compact, fast restart, but data loss between snapshots). AOF: append-only log of every write command (better durability, configurable fsync, but larger files and slower restarts). Hybrid: RDB baseline + AOF for incremental writes.

Question

What is hash tagging in Redis Cluster?

Answer

A technique to force multiple keys to the same slot by wrapping part of the key in {}. Example: {user:123}:name and {user:123}:profile both hash to the same slot, enabling atomic MULTI/EXEC transactions across those keys.

Question

What is the gossip protocol in Redis Cluster?

Answer

A decentralized failure detection mechanism where each node periodically pings random peers and shares its view of cluster state. Information spreads in O(log N) rounds. If a majority agrees a node is down (PFAIL → FAIL), failover is triggered.

Question

What is cache-aside pattern?

Answer

The application manages caching explicitly: check cache first (HIT = return, MISS = query DB, store in cache, return). On writes: update DB then invalidate cache. Most common pattern due to simplicity, but cache misses incur 3 round-trips.

Question

What is the WAIT command in Redis?

Answer

A command that blocks until the specified number of replicas have acknowledged a write. Example: SET key value; WAIT 1 500 blocks until 1 replica confirms (timeout 500ms). Provides strong consistency at the cost of increased write latency.

Question

What is the difference between LRU and LFU eviction?

Answer

LRU (Least Recently Used): evicts the key accessed longest ago. Good for temporal locality (recently used = likely used again). LFU (Least Frequently Used): evicts the key with fewest accesses. Good for frequency-based patterns (popular items stay cached).

Question

What is a MOVED redirect in Redis Cluster?

Answer

When a client sends a command to the wrong node, the node responds with MOVED <slot> <node-ip:port>. The client updates its slot map and retries the command to the correct node. This is how clients discover the cluster topology.

Question

What is connection pooling in a Redis client?

Answer

Maintaining a pool of pre-established TCP connections to each Redis node, reused across requests. Eliminates TCP handshake overhead (~1ms per connection), limits concurrent connections, and enables automatic failover to healthy connections.

Revision Notes

Key Takeaways

  • 1.Redis Cluster uses 16384 hash slots for deterministic, efficient data distribution and rebalancing
  • 2.Async replication is the default; use WAIT command only when strong consistency is required
  • 3.Cache-aside is the most common pattern but cache misses cost 3 network round-trips
  • 4.LRU is for temporal locality; LFU is for frequency-based access patterns
  • 5.Gossip protocol enables decentralized failure detection without a single point of failure
  • 6.Connection pooling and pipelining are essential for sub-millisecond client-side performance
  • 7.Hybrid persistence (RDB + AOF) balances durability with restart speed
  • 8.Hash tags ({}) are required for multi-key atomic operations across the same slot

Interview Tips

  • Start by clarifying requirements: data size, read/write ratio, latency targets, consistency needs
  • Draw the shard-based architecture with primary-replica pairs before discussing partitioning
  • Explain consistent hashing or slot-based partitioning with a concrete example (show the math)
  • Discuss the CAP theorem trade-off: Redis favors AP (availability + partition tolerance) by default
  • Show you understand failure modes: node failure, network partition, split-brain
  • Explain cache patterns with a read/write flow diagram
  • Know the eviction policies and when to use each (LRU vs LFU is a common follow-up)
  • For memory management, mention jemalloc and fragmentation ratio as operational concerns

Cheat Sheet

Distributed Cache (Redis Cluster) - Cheat Sheet

Architecture

Client SDK → Cache Cluster (N nodes, each with replicas) → Persistence
                    ↓
          Cluster Manager (Sentinel/Raft)

Partitioning

  • Redis Cluster: 16384 hash slots, slot = CRC16(key) mod 16384
  • Hash Tags: {tag}key forces keys to same slot for atomic operations
  • Consistent Hashing: Alternative, uses hash ring with virtual nodes

Replication

  • Async primary-replica per shard (default)
  • Strong consistency via: WAIT <replicas> <timeout>
  • Failover: Sentinel/Raft detects failure, promotes replica (5-15s)

Eviction Policies

Policy When to Use
LRU Temporal locality (recently used = likely used again)
LFU Frequency locality (popular items stay cached)
TTL Time-sensitive data
No-eviction When data must not be lost

Cache Patterns

Pattern Read Write Latency
Cache-Aside Check cache → DB → store Update DB → invalidate Miss: 3 RTTs
Read-Through Cache loads from DB Update DB → invalidate Miss: 2 RTTs
Write-Through Cache loads from DB Write cache → sync DB Write: 2 RTTs
Write-Behind Cache loads from DB Write cache → async DB Write: 1 RTT

Persistence

Type Durability Restart Speed Trade-off
RDB Snapshot interval Fast (compact file) Data loss between snapshots
AOF everysec 1s data loss Slow (replay log) Larger files
Hybrid Best Medium Configured via aof-use-rdb-preamble

Client-Side

  • Connection Pooling: Pre-established connections, reuse across requests
  • Pipeline: Batch commands in single RTT
  • MGET/MSET: Batch reads/writes
  • Retry: Handle MOVED (slot migration) and ASK (in-progress migration)

Memory

  • mem_fragmentation_ratio: RSS/used_memory, healthy 1.0-1.5
  • jemalloc: Default allocator, reduces fragmentation
  • maxmemory: Set memory limit, triggers eviction

Key Interview Points

  1. 16384 slots: deterministic routing + efficient rebalancing
  2. Async replication is default; WAIT for strong consistency
  3. Cache-aside is most common; know the 3 RTT miss penalty
  4. LRU vs LFU: temporal vs frequency locality
  5. Gossip protocol for decentralized failure detection
  6. Connection pooling eliminates TCP overhead
  7. Pipeline reduces multiple RTTs to one