12. Caching: The Basics
Practice: five levels
Assignments
Five levels. Do them in order. Answers are in "Solutions" at the end of the chapter, so try first.
Level 1: Think (no code)
For each item, decide: cache it or not? If yes, suggest a TTL and say why. Use "Cache what can REST, skip the three Us."
- The list of countries in a sign-up form dropdown.
- A user's bank balance on the "confirm payment" screen.
- The "trending now" list on a shopping app's home page.
- Search results for whatever random text a user types.
- A user's profile photo URL, shown next to every comment they post.
- The "likes" count on product reviews, updated thousands of times a minute. Which write strategy fits, and why?
Level 2: Calculate
Your app gets 1,000 requests per second for product pages.
- A cache read takes 2 ms. A database read takes 40 ms.
- Your cache has an 80% hit rate. (Ignore the small cost of checking the cache on a miss.)
- What's the average response time?
- How many queries per second reach the database?
- Your database starts struggling above 100 queries per second. What hit rate do you need to stay under that?
- After a deploy, 10,000 product keys are refilled within the same second, all with a 300-second TTL. What happens 300 seconds later? What TTL range would spread those expiries over about a minute?
Level 3: Build
Write a small in-memory cache class, TTLCache(capacity, ttl_seconds), with:
get(key): returns the value, orNoneif the key is missing or expired.set(key, value): stores the value with an expiry time.delete(key): removes the key.- LRU eviction: when there are more than
capacityitems, remove the least recently used one. hit_rate(): hits ÷ (hits + misses).
Then use it in front of a fake slow database (get_price, update_price) and check that:
- Five reads of the same key call the database only once.
- After
update_price, the next read returns the new price. - With
capacity=2, reading a third key evicts the least recently used one. - After the TTL passes, a read goes back to the database.
Extension: add (a) a WriteBack wrapper whose writes reach the database only on flush(), and show that a crash before the flush loses the write; and (b) TTL jitter, showing that 10,000 keys no longer expire in the same second.
Hint: Python's collections.OrderedDict has move_to_end() and popitem(last=False). Pass in a fake clock so tests don't have to sleep.
Level 4: Debug
Users complain: "After the admin changes a price, some of us still see the old price for up to 10 minutes." Here's the code:
- Find the bug. Explain step by step how a user ends up seeing the old price for 10 minutes.
- Fix it.
Level 5: Design
You're designing the product page for an online store's biggest sale of the year.
- 5 million users open product pages in the first 10 minutes.
- 200 products are on sale. 10 of them get most of the traffic.
- Prices can change during the sale (flash discounts), and a user must never pay a wrong price.
- Stock changes every second.
Deliver:
- What do you cache, and what do you never cache?
- Which cache layers do you use (browser, CDN, Redis, in-app)?
- What TTL for each kind of data, and why?
- How do you make sure checkout never uses a stale price?
- What breaks first if the cache goes down, and how do you protect the database?