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

Caching Strategies & Layer Placement

How modern web applications serve 95%+ of incoming traffic in under 1 millisecond. Master Cache-Aside, Write-Through, Write-Back, LRU eviction, and the 3 classic cache disasters: Penetration, Avalanche, and Thundering Herd.

โฑ๏ธ Reading Time: ~25 min
๐ŸŽฏ Technologies: Redis, Memcached, Guava/Caffeine, CDN
๐ŸŽฌ Format: Desk Analogy + Step-by-Step SVGs + Live Lab

โšก 1. Why Caching is Mandatory (Hardware Physics)

๐Ÿ“ The Simple Analogy: A Sticky Note on Your Desk vs The Basement Library

Imagine you're solving math problems at your desk.

If you keep key formulas on a sticky note on your desk (RAM Cache), glancing at it takes 0.5 seconds. If you have to walk down to the basement library (Disk Database) every single time you need a formula, it takes 15 minutes!

In computers, reading data from RAM is over 100,000ร— faster than reading from persistent disk storage:

โšก L1 CPU Cache~1 nsOn-chip hardware silicon
๐Ÿš€ Main Memory (RAM)~100 nsRedis / Memcached speed
๐Ÿ’พ NVMe SSD Storage~100,000 ns (0.1ms)1,000ร— slower than RAM
๐ŸŒ Database Disk Query~10,000,000 ns (10ms)100,000ร— slower than RAM
๐Ÿ’ก The 80/20 Pareto Principle of Web Traffic:In almost every platform (Instagram, Amazon, Twitter, Netflix), 80% of all user requests query just 20% of your data (trending posts, top products, active user sessions). By storing that 20% in RAM, 80%+ of database queries disappear!

๐ŸŒ 2. The 5 Cache Placement Tiers in Production

Where can you place caches between the user's phone and your database? There are 5 distinct layers:

1. Client / Browser Cache:Images, CSS, JavaScript, and user session tokens stored directly in the user's phone or browser storage via HTTP headers (Cache-Control: max-age=3600). Latency = 0ms!
2. CDN Edge Cache (Cloudflare / CloudFront):Servers distributed across 300+ global cities caching static media and public API responses close to the user. Latency = ~5ms.
3. Reverse Proxy Cache (Nginx / Varnish):Placed in front of backend microservices to cache frequent JSON responses without waking up application worker processes.
4. Application Process Memory (Guava / Caffeine):In-memory hash maps inside your Java / Node.js process heap. Extremely fast (0.01ms), but not shared across servers.
5. Distributed Cache (Redis / Memcached):A dedicated in-memory cluster shared across all 100+ backend servers. Holds terabytes of session states, user profiles, and query results. Latency = <1ms.

๐Ÿ”„ 3. The 4 Classic Read & Write Caching Patterns

How should your application coordinate reads and writes between the cache and the primary database?

1. Cache-Aside (Lazy Loading)

The application checks the cache first. If found (Hit), return it. If missing (Miss), fetch from DB, save into Cache, and return to user.

โ€ข Pros: Highly resilient. If Redis crashes, app queries DB directly.
โ€ข Rule: On write, DELETE the cache key, never update it in place!
โ€ข Best for: General web read-heavy workloads (User profiles, blog posts).
2. Write-Through

The application treats the cache as the main database. When writing data, the cache synchronously updates the database before returning success.

โ€ข Pros: Zero stale data. Cache is always 100% in sync with DB.
โ€ข Cons: Higher write latency (writes must succeed in both RAM and disk).
โ€ข Best for: Financial balances, core user metadata.
3. Write-Back (Write-Behind)

The application writes only to Cache and gets an instant 0.5ms ACK. The cache asynchronously batches writes and flushes them to the DB in the background.

โ€ข Pros: Blazing fast write speed. Perfect for 100,000 writes/sec.
โ€ข Danger: If Redis crashes before flushing to disk, data is lost.
โ€ข Best for: Real-time analytics, YouTube video view count counters.
4. Write-Around

Writes bypass the cache completely and write straight to the database. Only when that data is read later does it enter the cache.

โ€ข Pros: Prevents cache pollution for data that is written once and rarely read.
โ€ข Best for: Archival logs, legal contracts, invoice PDFs.

๐Ÿ’ฅ 4. The 3 Classic Distributed Cache Disasters (Interview Favorites)

In high-scale production, three specific failure modes can bring down your entire data infrastructure. Here is how they happen and how senior engineers prevent them:

Disaster 1Cache Penetration (Querying Non-Existent Keys)

The Attack: A bot sends 100,000 requests/sec for non-existent IDs (e.g. GET /user/-99999). Because the key does not exist in Redis, every request misses cache and hammers the SQL database directly.

๐Ÿ› ๏ธ The Fix: Put a Bloom Filter in front of Redis to reject non-existent keys in O(1) time, or cache a NULL value with a short 60-second TTL.
Disaster 2Cache Breakdown / Thundering Herd (Hot Key Expiry)

The Scenario: A viral breaking news article receiving 50,000 requests/sec has its cache key expire at 12:00:00. At that exact millisecond, all 50,000 requests miss cache simultaneously and rush to query the database to rebuild the cache, melting the DB.

๐Ÿ› ๏ธ The Fix: Use a Singleflight / Distributed Mutex Lock (SETNX) so only one thread queries the DB to rebuild the cache while all others wait.
Disaster 3Cache Avalanche (Simultaneous Mass Expiration)

The Scenario: At midnight, an e-commerce platform caches 500,000 products with a fixed TTL = 2 hours (7200s). At exactly 2:00:00 AM, all 500,000 keys expire at the same second. An avalanche of requests crashes the database.

๐Ÿ› ๏ธ The Fix: Add Random TTL Jitter: TTL = 7200 + rand(0, 300). This scatters expiration smoothly over a 5-minute window!

๐Ÿ’ป 5. Production Cache-Aside with Mutex Lock (Thundering Herd Defense)

Below is the production implementation of Cache-Aside with a Mutex Lock to prevent Thundering Herd spikes:

Select Language:
// Python 3.10+ Cache-Aside with Mutex Lock (SETNX)
import time, random

class CacheManager:
    def __init__(self, redis_client, db_client):
        self.redis = redis_client
        self.db = db_client

    def get_product(self, product_id: str) -> str:
        cache_key = f"prod:{product_id}"
        val = self.redis.get(cache_key)

        # 1. Cache HIT
        if val is not None: return val

        # 2. Cache MISS: Acquire distributed lock to prevent Thundering Herd
        lock_key = f"lock:{product_id}"
        if self.redis.set(lock_key, "1", nx=True, ex=5):
            try:
                val = self.db.query_product(product_id)
                # Add random TTL jitter (3600s + rand) to prevent Cache Avalanche
                ttl = 3600 + random.randint(0, 300)
                self.redis.set(cache_key, val, ex=ttl)
                return val
            finally:
                self.redis.delete(lock_key)
        else:
            # Another thread is rebuilding cache; sleep 50ms and retry
            time.sleep(0.05)
            return self.get_product(product_id)

๐Ÿงช 6. Interactive Lab: Live Cache & Thundering Herd Simulator

โšก Real-Time Caching Engine

Interactive Cache Pattern & Thundering Herd Lab

Fire requests, simulate stampedes, and test Singleflight mutex locks!
1. Caching Strategy:
2. Thundering Herd Mutex Lock:
3. Traffic Spikes:
Total Requests:0
Cache Hits (RAM):0
Database Queries:0
Cache Hit Rate:100%
๐Ÿš€ Cache Ready: Click "๐Ÿ‘ค 1 Read" for normal traffic or "โšก 10,000 Stampede" to test Thundering Herd defense!

๐ŸŽฏ 7. Test Your Understanding

Scenario 1: The Breaking News Stampede

A breaking news article receiving 50,000 requests/second has its cache key expire. Within 500ms, the primary SQL database crashes with CPU at 100%. What disaster happened and what is the fix?

Scenario 2: Cache Update vs Invalidation

When a user updates their profile in the database, why do production distributed systems DELETE the cache key rather than UPDATING it in place?

โ—‹

Completed this module?

Mark as finished to update your curriculum progress tracker.