open atlas
↑ Back to track
System Design Foundations SD · 05 · 07

Caching at scale: code and config reading

Read real caching code and config, then reason about behavior: a stale-read race, a miss-rate-to-DB-load calculation, a noeviction misconfiguration, and a hot-key stampede in a read path.

SD Senior ◷ 14 min
Level
FoundationsJuniorMiddleSenior

Caching bugs live in the code and config, not the prose: a missing invalidation, a synchronous miss path, a default eviction policy, a hot key read without coalescing. Read each snippet, reason about what it does under load, and pick the answer a senior engineer would commit to.

Practise the loop you run in a caching design or incident review: locate the strategy in the code, predict its behavior under concurrency and load, and choose the change the mechanics actually support.

Snippet 1 — the invalidation order

# cache-aside read + "delete on write" invalidation
def get_user(uid):
    u = cache.get(f"user:{uid}")
    if u is None:                       # miss
        u = db.read_user(uid)           # slow load of CURRENT value
        cache.set(f"user:{uid}", u)     # populate (no TTL)
    return u

def update_user(uid, patch):
    db.write_user(uid, patch)           # new value to DB
    cache.delete(f"user:{uid}")         # invalidate
Quiz

Under concurrency, a user's stale value occasionally survives an update forever. What is the bug, and the most robust fix?

Snippet 2 — the hit-ratio math

def db_reads_per_sec(total_rps: float, hit_ratio: float) -> float:
    miss_rate = 1.0 - hit_ratio
    return total_rps * miss_rate

before = db_reads_per_sec(80_000, 0.99)   # ?
after  = db_reads_per_sec(80_000, 0.90)   # ?
Quiz

What do before and after return, and what's the takeaway for the database?

Snippet 3 — the Redis cache config

# redis instance used purely as a cache
maxmemory 16gb
maxmemory-policy noeviction        # left at default
# app issues SET ... EX 600 for every key
Quiz

What happens when this cache fills its 16GB, and what should the policy be?

Snippet 4 — the hot-key read path

# product page for a viral item, recompute is ~300ms
def get_page(pid):
    html = cache.get(f"page:{pid}")
    if html is None:                    # miss
        html = render_page(pid)         # expensive: 6 queries + render
        cache.set(f"page:{pid}", html, ex=30)
    return html
# at peak, ~40,000 concurrent requests for the SAME pid
Quiz

Every 30 seconds the backend is hit by thousands of simultaneous render_page calls for one pid. What's the failure and the fix?

Recall before you leave
  1. 01
    Why does delete-on-write race, and what's the robust fix?
  2. 02
    From a hit ratio, how do you compute DB load, and why is the asymmetry dangerous?
  3. 03
    Why is noeviction wrong for a cache, and how do you stop a hot-key stampede in a read path?
Recap

Every caching decision in this unit shows up directly in the code and config. Delete-on-write invalidation races under concurrency — a slow reader sets the old value back after the delete, and with no TTL it never recovers — so you use versioned keys (and a TTL backstop). The miss-rate math (DB reads = total × (1 − hit_ratio)) shows why 99%→90% hits is a 10× DB load spike: the DB feels the miss side, sized for ~1%. The noeviction default makes a Redis cache fail writes under memory pressure instead of dropping cold keys, so a pure cache wants allkeys-lru/lfu. And a hot-key read path without coalescing stampedes the backend on every expiry — thousands of simultaneous recomputes of one value — fixed by single-flight (one recompute, the rest wait) and optionally probabilistic early recompute. The senior habit is to read the mechanics, predict the behavior under load and concurrency, and choose the structural fix the code supports — not the one (a bigger TTL, more nodes, faster render) that merely papers over it.

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.