Earlier in this series, we established why caching exists, then looked at how a cache works — memory, key-value storage, TTL, eviction, locality, and performance.
Now comes an architectural question:
Where should the cache actually live?
This is where caching gets more interesting.
A cache doesn't have to be one Redis server sitting between your application and database. In a modern system, you might have several caching layers stacked between the user and the source of truth.
The closer the cache is to the user or application, the less work the request needs to do.
But there's a catch: every additional caching layer introduces another copy of your data — and another thing you have to manage.
1. Browser cache: the fastest cache you don't own
Let's start at the edge closest to the user: their browser.
Suppose you visit a website and download logo.png, app.js, styles.css, some fonts, and a set of product images. Do we really need to download those files every time you visit?
No. The browser can store them locally.
Browser
│
├── Do I already have it?
│
└── YES → use the local copy
In many cases the server doesn't even need to process the request. That's an enormous performance improvement, because the network disappears from the critical path entirely:
Without browser cache: With browser cache:
User User
↓ ↓
Internet Browser cache
↓ ↓
Server Response
↓
Response
But there is a problem
You don't completely control the browser's copy.
Imagine you release a new version of your JavaScript application. A user might still be holding app.js version 1 while your server is serving version 2.
This is why cache-control headers, versioned filenames, and content hashing matter so much:
app.abc123.js
app.def456.js
When the content changes, the filename changes too. The browser can safely cache the old version forever, because the new application asks for a different file.
2. CDN: bringing the cache closer to the user
Now imagine your application runs in Virginia, but your users are in New York, California, London, Singapore, and Tokyo. If every image request travels all the way to your application servers, you're doing unnecessary work.
This is where a Content Delivery Network (CDN) helps — it places cached copies at locations closer to users.
A user in Tokyo doesn't necessarily need to travel all the way to your origin server:
User → Tokyo edge → Response
instead of
User → Tokyo → Internet → Origin → Database
That's not just a latency optimisation. It can also dramatically reduce traffic hitting your origin infrastructure.
What commonly belongs in a CDN: images, JavaScript, CSS, fonts, videos, static files, and sometimes API responses.
But here's another important lesson: not everything should be cached at the CDN. Highly personalised or sensitive data requires much more careful handling.
3. Application cache: keep data close to your code
Now let's move inside the application.
Suppose your application frequently needs this information:
US → United States
IN → India
CA → Canada
UK → United Kingdom
Does it make sense to query a database every time? Probably not. You could keep this in memory instead, where retrieval is extremely quick. There is no database call. There isn't even a network call.
This is sometimes called a local cache or in-process cache.
Local cache: Distributed cache:
Application Application
↓ ↓
Memory Network
↓ ↓
Result Distributed cache
↓
Result
The local cache wins on raw latency. But now we have a problem.
4. The problem with local caches
Imagine you have three application servers, each with its own cache. All three are holding product:123 at $999.
Now someone changes the product price to $899. The instance that handled the write updates its own copy. The other two don't automatically know anything happened.
The cache is fast. But the copies aren't necessarily consistent.
This is one of the fundamental trade-offs in caching:
The closer the cache is to the application, the faster it can be — but the harder multiple copies can be to keep consistent.
5. Distributed cache: one shared cache
Instead of giving every application instance its own cache, we can introduce a shared one, as shown on the right side of the diagram above. All three application servers now read the same cached data, and the divergence problem goes away.
But we have introduced another trade-off. The cache is no longer inside the application, so instead of:
App → Memory
we now have:
App → Network → Cache
Still much faster than many database operations — but not free. And now the cache itself needs monitoring, scaling, failover, capacity planning, security, backup and recovery considerations, and operational ownership.
We're starting to see an important pattern. Caching isn't free. It moves cost around.
6. Database caching
Here's something many developers forget: the database itself is already using caches.
Databases typically keep frequently accessed data, indexes, pages, and other structures in memory. So when your application executes:
SELECT * FROM products WHERE id = 123;
the database may not need to read from physical storage at all. It may already have what it needs in memory.
Application
│
▼
Database
│
├── Memory cache → HIT
│
└── Disk → MISS
This matters because it means adding an application cache isn't automatically the first thing you should do. Sometimes the database is already performing well. Sometimes a missing index, an inefficient query, or an excessive number of round trips is the real problem.
Before adding a cache, understand the bottleneck.
7. Putting the layers together
Now let's look at a realistic request. A user opens a product page, and the request may encounter several layers before anything answers it.
What makes layered caching powerful is that different requests stop at different places:
Only when everything misses do we reach the database — and then the response travels back up, populating the layers on its way, so the next request stops sooner.
8. But should we add every layer?
Absolutely not. This is where caching becomes dangerous.
It is tempting to think:
Browser + CDN + Local cache + Redis + Database cache = maximum performance
Not necessarily. Every layer introduces complexity. Imagine a value that exists in the browser, the CDN, a local cache, Redis, and the database. Now ask:
- Which one is correct?
- If the value changes, which layers need to be invalidated?
- If Redis goes down, does the application continue?
- If the CDN serves stale content, is that acceptable?
- If the local cache holds an older value, how do we update it?
Suddenly our simple performance optimisation has become a distributed-systems problem.
9. So where should you put the cache?
| Cache location | Best for | Main trade-off |
|---|---|---|
| Browser | User-specific and static resources | Limited server control |
| CDN | Global, static content | Invalidation and personalisation |
| Local application memory | Tiny, frequently used data | Multiple copies |
| Distributed cache | Shared application data | Network and infrastructure |
| Database cache | Database internals | Doesn't eliminate DB requests |
There isn't one universally correct location. The right answer depends on what you're caching.
10. A simple decision framework
Before adding a cache, ask four questions.
Who needs this data?
Only one application instance? A local cache might work. Every instance? Consider a distributed cache. Every user? A CDN or browser cache may be appropriate.
How frequently does it change?
Rarely — longer TTLs become possible. Frequently — you need a stronger invalidation strategy.
How expensive is the original operation?
If the database query takes 2 ms, caching may not be worth much. If it takes 500 ms and happens thousands of times per second, caching becomes very interesting.
How much stale data can we tolerate?
This is perhaps the most important question. A few minutes of staleness is probably fine for a profile picture or a product description. Inventory counts are a harder call. A bank balance has very different requirements altogether.
The answer to that last question determines the caching strategy more than any benchmark will.
The bigger lesson
Caching isn't just about where to store data. It's about deciding:
How far are we willing to move away from the source of truth in exchange for speed?
The closer the cache is to the user, the faster the response can become. But the farther we move from the source of truth, the more carefully we need to think about freshness, invalidation, consistency, failures, observability, and operational complexity.
That's why experienced engineers don't simply ask "should we use Redis?" They ask:
"What problem are we trying to solve, and which caching layer solves it with the least complexity?"
What's next
So far we've covered why caching exists, how caching works, and where caching lives.
But we still haven't answered a critical question: when the application reads or writes data, exactly how should the cache participate?
Should the application talk to the cache first? Should the cache automatically load missing data? Should writes go to the cache first? Should the cache write to the database later?
These are caching patterns, and choosing the wrong one can create subtle production problems. That's where we'll go next.
How many caching layers does your production system actually have — and could you name what invalidates each one? I'd genuinely like to know how many people can answer that second part.




Top comments (0)