80/20 Of The Week … Caching (Part 2)
Part 1 was about what caching is, this part is about how it is usually wired in real systems.
- The most common flow: cache-aside
A very common implementation looks like this:
- request comes in for
user:123 - app checks Redis for
user:123 - if found, return it
- if not, fetch from DB
- put result into Redis with a TTL
- return response
But if many requests miss together, they all fall through to the DB unless you add protection.
That is why real systems add:
- request coalescing
- per-key locking
- background refresh on hot keys
Amazon’s DAX follows a very similar flow for DynamoDB reads: check cache first, otherwise fetch from DynamoDB and write the result into cache.
- Writes are where things get interesting
A typical write flow looks like:
- update row in DB
- invalidate Redis key, or update cached value too
- future reads refill cache or read fresh data
Looks fine… until ordering breaks.
Meta gave a very concrete example:
an old cache fill with x=42 can arrive after the DB was already updated to x=43, so cache ends up with old data while DB has the new value.
That is why cache invalidation is painful: not because deleting a key is hard, but because distributed timing is hard.
- Replica lag can poison the cache too
A common read path in bigger systems is:
- writes go to primary
- reads go to replica
- app reads from replica
- app stores that result in Redis
If the replica is behind, Redis can end up caching old data even though the cache itself did nothing wrong.
So sometimes “stale cache” is really a stale replica read that got cached.
AWS documents a similar tradeoff in DAX: eventually consistent reads are cached, while strongly consistent reads bypass DAX.
- Query caches are harder than key caches
Caching by key is relatively simple:
user:123 -> {...}
Query caching is messier:
top-products?page=1®ion=EU -> [...]
One product update may affect many cached pages, filters, or result sets.
That is why query/result caches usually need shorter TTLs and more careful tuning than simple key-based reads.
- Discord used Redis in a practical way
Discord described a pattern in its search pipeline where Redis was used to mark shard + guild search data as “dirty” with an expiration.
Then on search:
- look up shard
- check Redis dirty flag
- if dirty, refresh Elasticsearch index
- mark clean
- run search
So Redis there is not just a generic cache.
It acts like a fast shared freshness map.
- Redis becomes infrastructure very quickly
In practice, Redis often ends up holding:
- object cache entries
- TTL-based lookups
- dirty flags
- counters
- routing metadata
- temporary coordination state
That is one reason it shows up everywhere: it is fast, shared, and fits a lot of TTL-driven production patterns well.
TLDR
In real systems, caching is about the full lifecycle:
- who fills the cache
- who invalidates it
- where the data came from
- how long it lives
- what happens on misses
- what happens under load
That is why caching starts simple… and becomes a distributed systems topic very fast.
#Backend #SystemDesign #DistributedSystems #Caching #Redis #Microservices




