A discussion platform has a handful of extremely popular threads receiving most reads and writes, and individual cache nodes become bottlenecks. How should hot-key detection, caching, coalescing, partitioning and asynchronous writes combine?
Show the full answer Hide the answer
Why ordinary caching is insufficient
Caching assumes load spreads across keys. Here it does not: a small number of threads receive the overwhelming majority of traffic. The cached entry for one thread lives on one node, so that node receives a disproportionate share of the fleet's requests regardless of how many nodes exist. Adding cache capacity does nothing.
Worse, hot threads are hot for writes too — comments and votes arrive continuously — so the cached value is constantly invalidated, which is the pathological case: maximum read volume against maximum churn.
The combination that works
1. Detect hotness explicitly. Per-key counters with a top-N sketch. Aggregate hit rates hide the problem entirely; you need to know which keys are hot to treat them differently, and that treatment is the whole design.
2. Replicate hot keys across cache nodes. Once a key crosses a threshold, place it on several nodes and have clients pick randomly. This is the direct fix for a single-node bottleneck, and it costs a small amount of extra staleness between replicas — invisible for a comment count.
3. Local in-process caches for the hottest keys, with very short TTLs. A one-second cache in each application instance removes almost all remote reads for a hot thread, at the cost of one second of staleness. For a discussion thread this is an outstanding trade.
4. Request coalescing on miss. When the hot entry expires, thousands of concurrent requests miss simultaneously. Single-flight collapses them into one regeneration; everyone else waits on it. Without this, expiry of a hot key is a self-inflicted denial of service against the database.
5. Jittered TTLs, so replicas do not all expire at the same instant and recreate the stampede on a schedule.
6. Asynchronous, batched writes for counters. Votes and view counts are aggregated in memory and flushed periodically rather than each incrementing a row. This is the key move on the write side: it turns thousands of writes per second on one row into one write per interval. It requires accepting that counts are approximate and briefly lossy — which is exactly the guarantee a vote count needs and no more.
7. Partition the thread itself. A thread's comments are not one object. Paginate and cache by page, so the hot entity is many cacheable units rather than one enormous one, and a new comment invalidates one page rather than the whole thread.
The consistency position, stated honestly
Under this design, a hot thread's comment count is approximate, a new comment may take a second to appear, and two users may briefly see different counts. Every one of those is invisible to users and each is essential to the design working at all.
What must remain correct: the user's own comment appears immediately (read-your-writes on your own action), the vote is durably recorded even though the aggregate is approximate, and moderation actions take effect promptly — which usually means an explicit invalidation path rather than waiting for a TTL.
The generalisable rule
For skewed workloads, the goal is not to serve the hot key faster. It is to ensure the expensive work happens once per interval and everyone else reads the result — through replication, coalescing, local caches and batched writes working together, because any one of them alone leaves a bottleneck.