When a hot key expires, let one request query the database while every other request for that key joins it, shares its result or its error, and leaves nothing stuck behind.
A hot key expires and, in the next few milliseconds, every request for it misses the cache and queries the database: with 10,000 requests a second and 30% of them on one key, that's about 3,000 queries a second for a single row (Day 3, section 1.3). Request coalescing (section 2.2, also called single flight) lets the first request fetch and makes every other request for the same key join that in-flight fetch and share its result.
Build CoalescingCache(db, clock, ttl=300), a cache-aside cache whose get(key) is a generator: callers run it with value = yield from cache.get(key) (JavaScript: const value = yield* cache.get(key)). Every yield is a point where other requests may run, the way an await is in the lesson's asyncio version. The database is a generator too: value = yield from self.db.fetch(key) yields while the query runs, then returns the value (None for a missing row) or raises DbError.
get(key):
clock.now() < expires_at. Return the value without yielding.db.fetch(key), then:
clock.now() + ttl (measured when the fetch finishes) and return it. Don't cache None: a missing row is fetched again next time.DbError, cache nothing and raise it. Every joined request raises it too.in_flight_count() returns how many keys have a fetch in flight. Two different keys never share a fetch.
Given: FakeClock, DbError, FakeDB (counts queries; latency_steps yields per query; set fail_next to make the next completing query fail), and run_all(generators, clock=None, tick=0), which steps the generators round-robin until all are done (advancing the clock by tick after each round) and returns ("ok", value) or ("error", exception) per generator (JavaScript: { ok: true, value } or { ok: false, error }).
In JavaScript and TypeScript: new CoalescingCache(db, clock, ttl = 300) with *get(key) and inFlightCount(); a missing row is null.
Coalescing only matters when requests overlap, and code that never pauses never overlaps. The generator's yields are the pauses, so the tests and the chaos runs can interleave many requests deterministically, one step at a time. The bug they hunt for is the classic one: checking the in-flight table, pausing, and only then registering, so two requests both decide they're the leader.
Write your code, then run 3 example tests and 9 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.