Caching
TTL, eviction, and stale data
Before you start
- Open lesson
Caching
Cache-aside is the read path everything here builds on.
Why it matters
Ana’s profile is read constantly and edited rarely. A copy that still says “Ana” after she changes her name to “Ana Lee” is fast but wrong, and a long expiry can expose that wrong name to every follower. Normal post-edit reads should reload “Ana Lee” immediately, while a TTL must still bound stale data if invalidation is lost or races with a read.
Example
Cache a profile
A profile page is read constantly and edited rarely. Followers should not see a stale name for long after an edit.
- Input
- ttl = 60, db = {"u1": "Ana"}
- Output
- ["Ana", "Ana", None, "Ana Lee", "Ana Lee", "Ana Lee"]
Approach 2 · Resolve the bottleneck
The idea
Keep one example in view
Cache a profile
ttl = 60, db = {"u1": "Ana"}→["Ana", "Ana", None, "Ana Lee", "Ana Lee", "Ana Lee"]
The baseline, TTL only, is stale up to 60 s · O(n). The better model below keeps this exact input and removes that bottleneck.
A 60-second TTL alone returns “Ana” after the edit to “Ana Lee.” Delete u1 after the database write so this trace’s read at 45 reloads the new name, and keep the TTL to bound a lost invalidation or stale refill.
At time 0, cache u1 → “Ana” with expiry 60. A read at 20 hits that entry. When the edit arrives at 30, commit “Ana Lee” to the database first, then delete u1; the read at 45 misses and reloads the committed name.
The 60-second TTL remains a safety bound, not the normal freshness mechanism. This sequential trace makes the read at 45 fresh. Under concurrency, a miss can read “Ana” before the edit and refill it after the delete; versioned keys or fills coordinated with writes close that race.
Pro
The rest of this is on Pro
The idea above is free. The walkthrough, the cost, and the practice are on Pro.