Book Five · War
The Final Battle
The task
Design the complete caching strategy for the storefront, across every realm.
Bring together the techniques from the earlier lessons to design one complete caching strategy.
The rubric
| # | Requirement | From |
|---|---|---|
| 1 | Every one of the seven parts assigned to a realm — or marked never cached, with a reason | L1–L7 |
| 2 | A cache key and a TTL for each cached part | L2, L10 |
| 3 | A read strategy and a write strategy for the application layer | L8, L9 |
| 4 | An invalidation path for every mutable part, across every realm that holds it | L11 |
| 5 | A named defense for each of the five attacks | L12 |
| 6 | An instrumentation plan — what you measure, what alerts you | L13 |
| 7 | A stated failure mode: what happens when the cache dies | L6, L13 |
A worked example
Here is a worked example for the price, covering each requirement. Use the same structure for the other six parts.
Element: Price
1. Realms — Browser, CDN and Redis, with a 5-second local cache for the most popular products.
2. Keys and TTLs
| Realm | Key | TTL | Why |
|---|---|---|---|
| Browser | URL + currency + customer tier | max-age=10, must-revalidate | A browser cannot be purged, so 10 seconds caps reuse without revalidation |
| CDN | URL + currency + customer tier | 300s, purged on price change | A purge updates shared copies promptly; alert if it fails |
| Redis | price:{id}:{currency}:{tier} | 300s + jitter | Jitter prevents avalanche (L10) |
| Local cache | price:{id}:{currency}:{tier} | 5s | Absorbs hot-key traffic before it reaches Redis |
Currency and tier appear in every key that has a key at all. They change the answer, so they are in the key (L2).
3. Read and write
- Read: validate the ID, check local memory and Redis, then apply the Bloom-filter check on a miss before the database lookup. Use single-flight to combine concurrent requests for the same price key (L8, L12).
- Write: write-around — update the database, delete the cached copy (L9)
- Invalidate after the database update: this avoids the write-ordering problem, though concurrent reads still need protection (L11)
4. Invalidation path
Admin changes price
↓
Database updated immediate
↓
Local caches invalidate affected price keys immediate
↓
Redis invalidates affected price keys immediate
↓
CDN purges affected product responses monitor completion
↓
Browser: no direct mechanism — bounded by its TTL up to 10s
The browser reuse limit is 10 seconds. Staleness across the system also depends on successful invalidation delivery and CDN purges, so monitor both.
5. Attack defenses
| Attack | Defense |
|---|---|
| Stampede | Single-flight + refresh-ahead on top-100 products (L8) |
| Penetration | ID validation, a current product-ID Bloom filter, and 60-second negative caching for confirmed misses; rate-limit requests (L12) |
| Avalanche | TTL jitter on every price key (L10) |
| Hot key | 5-second local cache in front of Redis for top products (L5) |
| Poisoning | Currency and tier in the key; public is safe only because price is not per-user (L2, L12) |
Bloom-filter policy: use the Lesson 12 filter to check product IDs, not prices or stock. A possible match still needs the normal lookup, and price cache keys must still include currency and tier. Before publishing a new product, add its ID to every filter used for rejection and clear any negative cache entry. If coverage is uncertain, bypass the filter.
6. Instrumentation
- Hit ratio for the
price:prefix specifically — not the global number (L13) - p99 latency on price lookups; alert if it exceeds 100ms
- Track Bloom-filter rejections, bypasses, update failures and capacity; compare possible matches with database misses to spot wasted lookups
- Alert on stale-price reports with
Ageattached, to identify the realm - Track invalidation-event and CDN-purge failures — either can leave a stale price in place
7. Failure mode
If Redis is unavailable, bypass its cache and any Bloom filter it hosts. Keep rate limits and database concurrency limits active so fallback traffic cannot grow without bound; return a temporary error when capacity is exhausted. Filter failures and database errors must never become “product not found” responses. For this example, assume the database can handle four times its usual load. Verify that assumption with a staging load test.
Do the other six
Product details. Product images. The asset bundle. Stock count. Reviews.
For this storefront, leave the cart badge response uncached to keep the policy simple. It belongs to one user and must never be reused as another user's answer. Cart data stored in Redis under a user-specific key is a separate decision.
The cart has needed a separate policy throughout the course. Requirement 1 is satisfied by explaining precisely why it is not shared.
The final assessment
When you can produce that document for a real system — and defend every TTL in it — you have finished this course.
The end
The Surge arrives.
It arrives at the browser cache, and most of it stops there — never reaching the network at all.
What continues arrives at the edge, spread across the world, and is answered locally by copies already waiting.
What continues past that arrives at the application, and finds its answers in memory. The keys that are hot are already held close. The values approaching expiry have already been refreshed. The thousand requests that arrive for one missing key send one query and share its answer.
What reaches the database is a trickle. The database, which has been quietly caching all along, does not even notice.
And the Latency Lord, who built five weapons out of your own timers and your own keys, watches his attacks meet defenses you built throughout the course.
No single technique defeated him.
He was defeated by someone who had measured first.