SystemDesign.website
Track 4 ยท Module 4.2 ยท Caching & Memory Architecture

Distributed Caching, Redis Internals & Eviction

How Redis handles 100,000+ commands/sec on a single thread, scales horizontally across 16,384 Hash Slots, guarantees persistence with RDB & AOF, and evicts cold data using O(1) LRU & LFU algorithms.

โฑ๏ธ Reading Time: ~28 min
๐ŸŽฏ Core Topics: Single-Threaded Event Loop, Hash Slots, RDB/AOF, LRU Cache
๐ŸŽฌ Format: First-Principles Deep Dive + Interactive LRU Simulator

๐Ÿข 1. Why One Redis Server is Never Enough

๐Ÿ›’ The Analogy: A Fast Cashier vs 16 Checkout Lanes

A single Redis instance is like a lightning-fast supermarket cashier who can scan 100,000 items per second.

However, a single server has physical hardware limits:
1. Memory Limit: A standard cloud server typically caps out at 64 GB to 128 GB of RAM. If your application needs to cache 1 Terabyte of active user sessions, one machine cannot hold it.
2. Throughput Limit: At peak Black Friday traffic (500,000 queries/sec), a single CPU core network stack saturates.

To break through these physical limits, we need Distributed Caching (Redis Cluster):

Redis (Smart Cluster)

Automatic data sharding across 16,384 hash slots. Rich data structures (Strings, Hashes, Lists, Sets, Sorted Sets, HyperLogLog, Streams). Built-in master-replica failover.

Memcached (Dumb Server, Smart Client)

Multi-threaded key-value store with no disk persistence. The client library hashes keys locally to choose which server to talk to. Simple, fast, but lacks data types and replication.

โšก 2. Redis Internals: The Single-Threaded Event Loop

Almost every candidate in system design interviews asks: "Why is Redis single-threaded if modern CPUs have 64 cores?"

Redis creator Salvatore Sanfilippo chose a single-threaded execution model for three brilliant architectural reasons:

1. Zero Lock Contention (No Mutex Overhead):In multi-threaded databases (like MySQL or Java caches), threads spend huge amounts of CPU time acquiring mutex locks and waiting for other threads. Redis has zero locks โ€” every command executes sequentially without thread race conditions!
2. Zero CPU Context Switching:Switching between 1,000 OS threads costs 2โ€“5 microseconds per switch. By staying on 1 thread, the CPU instruction cache remains hot 100% of the time.
3. Non-Blocking I/O Multiplexing (epoll / kqueue):Redis uses the Linux epoll() system call. A single thread listens to 100,000 open TCP sockets simultaneously. When a client sends a command, epoll alerts Redis instantly to execute it from RAM in sub-microsecond time!
๐Ÿšจ The Golden Rule of Redis: Never Run O(N) Commands!Because Redis is single-threaded, running KEYS * or FLUSHALL on a database with 20M keys will block the single thread for 8 seconds. During those 8 seconds, EVERY other user request will time out! Always use cursor-based SCAN in production.

๐ŸŽฐ 3. Redis Cluster: The 16,384 Hash Slots

๐Ÿ“ฎ The Analogy: 16,384 Postal Mailboxes Divided Across 3 Post Offices

How does a Redis Cluster shard data across 3, 10, or 100 master servers without losing keys?

Redis divides the entire universe of keys into exactly 16,384 Hash Slots (numbered 0 to 16,383):

The Slot Allocation Formula:slot = CRC16(key) % 16384
Node A (Master 1)Holds Slots: 0 โ€“ 5460 (5,461 slots)
Node B (Master 2)Holds Slots: 5461 โ€“ 10922 (5,462 slots)
Node C (Master 3)Holds Slots: 10923 โ€“ 16383 (5,461 slots)
๐Ÿท๏ธ Pro-Tip: Redis Hash TagsIf you want multiple keys (like {user:101}.profile and {user:101}.orders) to always live on the exact same node for fast multi-key transactions, wrap the shared ID in curly braces: {user:101}. Redis will hash only the text inside the braces!

๐Ÿ’พ 4. Redis Persistence: RDB Snapshots vs AOF Logs

RAM is volatile. If power cuts out, all data in memory vanishes. Redis offers two distinct mechanisms to persist data to disk:

๐Ÿ“ธ 1. RDB (Snapshotting)

Redis forks a child process using Linux Copy-On-Write (COW) to dump the entire memory to a compact binary file (e.g. dump.rdb) every 5 minutes.

โ€ข Pros: Tiny backup files, blazing fast server restarts.
โ€ข Cons: If server crashes, you lose the last 5 minutes of writes.
๐Ÿ“œ 2. AOF (Append-Only File)

Redis logs every single write command sequentially to an append-only file on disk (e.g. appendonly.aof) with fsync everysec.

โ€ข Pros: Maximum durability (at most 1 second of data loss).
โ€ข Cons: Larger file size, slower server restart recovery.
๐ŸŒŸ Production Best Practice: Hybrid PersistenceRedis 4.0+ combines both! It writes an initial compact RDB snapshot as the base file, and appends AOF delta logs on top. This gives you lightning-fast restarts with zero data loss.

๐Ÿ—‘๏ธ 5. Cache Eviction Algorithms: LRU & LFU

When memory reaches maxmemory (e.g. 32 GB), Redis must evict older keys to make room for new incoming writes:

1. LRU (Least Recently Used) โ€” The Doubly-Linked List + Map Pattern

Whenever a key is accessed or written, move it to the Head of the list. When memory is full, instantly drop the key at the Tail! Lookup and eviction are both strictly O(1) time.

2. LFU (Least Frequently Used) โ€” The Frequency Counter

Tracks an 8-bit logarithmic access counter for each key with exponential decay. Evicts keys that are rarely accessed, protecting evergreen popular items from being flushed by a temporary batch scan.

๐Ÿ’ป 6. Implementing an O(1) LRU Cache (Multi-Language)

Here is the production data structure implementation of an O(1) LRU Cache using a Doubly-Linked List and Hash Map:

Select Language:
// Python 3.10+ O(1) LRU Cache
class Node:
    def __init__(self, key, val):
        self.key, self.val = key, val
        self.prev = self.next = None

class LRUCache:
    def __init__(self, capacity: int):
        self.cap = capacity
        self.cache = {}  # key -> Node
        self.head, self.tail = Node(0, 0), Node(0, 0)
        self.head.next, self.tail.prev = self.tail, self.head

    def _remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def _add_to_head(self, node):
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node

    def get(self, key: int) -> int:
        if key in self.cache:
            node = self.cache[key]
            self._remove(node)
            self._add_to_head(node)
            return node.val
        return -1

    def put(self, key: int, value: int):
        if key in self.cache:
            self._remove(self.cache[key])
        node = Node(key, value)
        self._add_to_head(node)
        self.cache[key] = node
        if len(self.cache) > self.cap:
            lru = self.tail.prev
            self._remove(lru)
            del self.cache[lru.key]

๐Ÿงช 7. Interactive Lab: Live LRU Eviction & Cluster Router

โšก Real-Time Memory Simulator

Interactive LRU Doubly-Linked List & Hash Slot Lab

Max Capacity = 4 Items
Live Doubly-Linked List: [HEAD (Most Recent) โž” TAIL (Eviction Target)]3 / 4 Items
๐Ÿš€ LRU Cache Ready: Click "โž• Insert New Key" to add items beyond capacity (4) and watch the oldest key get evicted from the Tail!

๐ŸŽฏ 8. Test Your Understanding

Scenario 1: The Midnight Batch Job Disaster

A nightly analytical job reads 5,000,000 old records through an LRU cache. The next morning, user read latency spikes from 1ms to 40ms because all popular evergreen data was evicted. Which eviction algorithm prevents this?

Scenario 2: Multi-Key Transactions in Redis Cluster

You need to perform an atomic transaction across two keys (user:101:balance and user:101:history) in a Redis Cluster. Why will standard Redis Cluster return a CROSSSLOT Keys in request don't hash to the same slot error, and how do you fix it?

โ—‹

Completed this module?

Mark as finished to update your curriculum progress tracker.