Bifalgorithm post
"Alright, let's say you want to design a caching system for your database. A user journey is simple. A user asks your backend for a social media profile. Your app first checks the cache. If the profile is there, you return it immediately. If it's not, you query the database, return the profile, and store it in the cache for next time. Simple. But let's build this system step by step because every version introduces a new problem. Version 1, the basic cache. Someone asks for a profile. Your backend checks Redis. If the data exists, that's called a cache hit. You return it directly without touching the database. If the data is missing, that's a cache miss. Now you query the database, get the profile, store it in Redis, and return it to the user. Next time somebody asks for that same profile, Redis already has it. Much faster. But now we have a problem. Imagine the user changes their username from Rick123 to Rick456. The database gets updated immediately, but Redis still contains Rick123. So your cache is now returning stale data. Version 2, expiration and invalidation. The easiest fix is adding a TTL. TTL means time to live. You could say every cache profile expires after 5 minutes. Once it expires, Redis deletes it. The next request misses the cache, goes back to the database and gets the newest version. But 5 minutes can be a long time. If thousands of users request that profile during those 5 minutes, they could all receive outdated data. So a better approach is cache invalidation. Whenever a profile changes in the database, you also delete that profile from the cache. Now the next request is forced to load the fresh version. Better. But there's another failure. Version 3, the cache stampede. Imagine one extremely popular profile is being requested thousands of times per second. Normally, Redis handles all of those requests. But then that cache entry expires. Suddenly, thousands of requests all miss the cache at the same time. And every one of them tries to query the database. Your database was receiving almost no traffic a second ago. Now it gets slammed with thousands of identical queries. The cache that was supposed to protect your database just became the reason it crashed. So how do we fix that? First, single flight. When a cache entry is missing, only the first request is allowed to query the database. Everyone else requesting that same key waits. The first request loads the value, stores it back in Redis, and then the waiting request can use it. One database query instead of thousands. Second, add jitter to your TTLs. Instead of every key expiring at exactly 5 minutes, maybe one expires at 5 minutes and 12 seconds, another at 4 minutes and 53 seconds, and another at 5 minutes and 20 seconds. That randomness prevents huge groups of keys from expiring at exactly the same moment. And finally, sometimes the problem isn't expiration, it's just raw traffic. One hotkey might receive so many reads that a single Redis node becomes the bottleneck. In that case, you can replicate the data across multiple Redis nodes and distribute the reads. So caching sounds simple. Check Redis, otherwise check the database. But real caching systems also need to think about stale data, invalidation, cache stampedes, expiration patterns, and hotkeys. That's where caching starts becoming a system design problem."
"Alright, let's say you want to design a caching system for your database. A user journey is simple. A user asks your backend for a social media profile. Your…
Ideas in this flowchart
- Designing a Caching System
- User Journey
- Check Cache
- Cache Hit
- Cache Miss
- Query Database
- Store in Cache
- Version 1: Basic Cache
- Stale Data Issue
- Version 2: Expiration & Invalidation
- TTL (Time to Live)
- Cache Invalidation
- Version 3: Cache Stampede
- Single Flight
- Add Jitter to TTLs
- Hotkey Traffic
- Replicate Data