1Show the Level 1 solution
21. **Countries list: cache it, long TTL (hours to a day).** Repeated, rarely changes, the same for everyone. It can even live in the browser or a CDN.
32. **Bank balance at payment: don't cache.** Urgent truth. Read it from the source of record at that moment.
43. **Trending list: cache it, short TTL (around 30 to 60 seconds).** Read by everyone, expensive to compute, and being slightly behind is fine.
54. **Random search text: usually don't cache the results.** Unique per request, so the hit rate would be near zero. (Caching the most popular searches can still help.)
65. **Profile photo URL: cache it, long TTL plus delete when the user changes their photo.** Read constantly, rarely changes.
76. **Likes count: write-back.** Count increments in the cache and flush totals to the database every few seconds. Thousands of writes collapse into a few, and losing a few seconds of likes in a crash is acceptable. (Never for money.)
Show the Level 2 solution
1. Average = 0.8 × 2 ms + 0.2 × 40 ms = 1.6 + 8 = **9.6 ms**.
2. Misses = 20% of 1,000 = **200 queries per second** to the database.
3. To stay at or under 100 queries per second, misses can be at most 100 ÷ 1,000 = 10%, so you need a hit rate of **at least 90%**.
4. All 10,000 keys **expire in the same second**, so 10,000 requests miss together and hit the database at once (an avalanche, Chapter 13). Use a TTL of **300 ± 30 seconds** (270 to 330) to spread them across about a minute; in the chapter's code, the busiest second drops from 10,000 expiries to about 200.
1Show the Level 3 solution
2Tested solution (Python 3, with a fake clock so tests don't sleep):
3
4```python
5from collections import OrderedDict
6
7
8class TTLCache:
9 """A small in-memory cache with a size limit (LRU eviction) and expiry (TTL)."""
10
11 def __init__(self, capacity, ttl_seconds, clock):
12 self.capacity, self.ttl, self.clock = capacity, ttl_seconds, clock
13 self.items = OrderedDict() # key -> (value, expires_at), oldest-used first
14 self.hits = self.misses = 0
15
16 def get(self, key):
17 entry = self.items.get(key)
18 if entry is None or entry[1] <= self.clock():
19 self.items.pop(key, None) # expired counts as a miss
20 self.misses += 1
21 return None
22 self.items.move_to_end(key) # mark as most recently used
23 self.hits += 1
24 return entry[0]
25
26 def set(self, key, value, ttl=None):
27 self.items[key] = (value, self.clock() + (self.ttl if ttl is None else ttl))
28 self.items.move_to_end(key)
29 if len(self.items) > self.capacity:
30 self.items.popitem(last=False) # evict the least recently used
31
32 def delete(self, key):
33 self.items.pop(key, None)
34
35 def hit_rate(self):
36 total = self.hits + self.misses
37 return self.hits / total if total else 0.0
38
39
40class WriteBack:
41 def __init__(self, cache, db):
42 self.cache, self.db, self.dirty = cache, db, set()
43
44 def write(self, key, value):
45 self.cache.set(key, value)
46 self.dirty.add(key) # remember what the database hasn't seen
47
48 def flush(self):
49 for k in sorted(self.dirty):
50 self.db.set(k, self.cache.items[k][0])
51 self.dirty.clear()
52```
53
54All four checks pass, plus the extension: the write-back wrapper writes nothing to the database until `flush()`, and a crash before flushing loses the unflushed price change; with ±30 s of jitter, 10,000 keys' busiest expiry second drops from 10,000 to 208. The full file (also with LFU, write-through, write-around and the Level 4 race) is in `code/ch12_caching.py`.
1Show the Level 4 solution
2**The bug:** the cache is deleted **before** the database is updated.
3
41. Admin calls `update_price("milk", 56)`. The cache key is deleted.
52. The database update starts. It takes about 200 ms.
63. During those 200 ms, a user requests milk. Cache miss.
74. That request reads the database, which **still says ₹52**, because the update hasn't finished.
85. It stores ₹52 in the cache with a **10-minute TTL**.
96. The database update finishes (₹56), but the cache now holds ₹52 for up to 10 minutes.
10
11**The fix:** update the database first, then delete the cache.
12
13```python
14def update_price(item, price):
15 db.update_price(item, price)
16 cache.delete(f"price:{item}")
17```
18
19Also consider a shorter TTL for prices, so any rare leftover staleness heals quickly. (The chapter's code simulates both orders: delete-first leaves ₹52 cached; update-first leaves ₹56.)
1Show the Level 5 (sketch) solution
21. **Cache:** product name, images, description, and display prices. **Never cache:** the price used at checkout, payment status, and the final stock check.
32. **Layers:** CDN for images and static assets; Redis for product data and display prices; optionally a tiny in-app cache for the 10 hottest products to take load off Redis.
43. **TTLs:** product details, hours plus delete on edit. Display prices, 30 to 60 seconds plus delete whenever a flash discount starts. Stock, a very short TTL for an approximate "few left" label only. Add jitter to all of them.
54. **Checkout:** read the price from the database at checkout and compare it with what the user saw. If it changed, show the new price before payment.
65. **If the cache goes down:** the database gets everything at once. Protect it with timeouts on cache calls, limits on database connections and queries, serving the CDN-cached page where possible, and warming the cache with the hot products before reopening traffic (Chapter 13).