TweakCode
Building Blocks lessons

Caching

TTL, eviction, and stale data

Before you start

  • Caching

    Cache-aside is the read path everything here builds on.

    Open lesson

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.