Cache Doesn't Make Your System Faster. It Just Moves the Complexity Somewhere Else.
There's a dangerous misconception in software engineering: that adding a cache is a performance solution. It isn't. It's a trade-off - one that swaps database load for a new set of problems around consistency, failure modes, and operational complexity.
Every caching decision ultimately comes down to one question: who is responsible for writing to the cache, and when does that write happen?
Answer that question differently and you get six distinct patterns. Each one makes sense in a specific context and falls apart in others. Here's a map of all six - not just what they are, but why you'd reach for each one.
1. Cache-Aside (Lazy Loading)
The pattern: The application manages the cache itself. On every read, it checks the cache first. If the data is there (a hit), return it immediately. If not (a miss), go to the database, fetch the data, write it into the cache for next time, and return it to the caller.
Why it's the most popular pattern: The application never has to trust the cache to be available. If Redis goes down, your app keeps working - it just queries the database directly on every request, which is slower but not broken. This graceful degradation is a huge operational advantage.
What you're paying for:
- Every cache miss costs three round trips: check cache → query DB → write to cache. That's three network hops instead of one.
- The first request for any piece of data is always slow - the cold start problem. After a deployment or a cache flush, users feel this.
- If two requests miss at the same moment, you can end up with a thundering herd - both threads query the database and both write to cache, duplicating work.
Best fit: High-read, low-write workloads where you can tolerate data being slightly stale within your TTL window. Product catalog pages, user profile lookups, configuration data.
2. Read-Through
The pattern: Superficially similar to Cache-Aside, but the key difference is who goes to the database. In Read-Through, the application talks only to the cache. When a miss occurs, the cache itself fetches the data from the database, stores it, and returns it. The app never touches the DB directly.
Why it's useful: The cache-population logic lives in one place - inside the cache provider configuration - rather than scattered across every service or function that reads data. Your application code becomes simpler: ask cache, get answer. No conditional logic, no fallback handling.
What you're paying for:
- You need a cache provider that actually supports this pattern (some do natively, others require plugins or proxies). You can't implement this with plain Redis and application code alone.
- More critically: the cache becomes a single point of failure. If it goes down, your entire read path goes with it, because the application no longer has the logic to reach the database directly. You've traded simplicity for fragility.
Best fit: Applications where code simplicity is paramount and the caching layer is highly reliable with its own redundancy and failover. Common in managed cache services where the provider handles the DB integration.
More details (Write-Through, Write-Behind, Write-Around, Refresh-Ahead) at:
6 Patterns of Cache

Top comments (0)