πŸ“š Study Notes / Home / HLD / Session 3
Session 03 Β· Caching 1

Caching: how systems remember answers so they don't redo work

Welcome to your first caching class. We assume you've studied none of this before. Every topic opens with a tiny "explain like I'm 5" story, then we slowly go deeper with real examples, diagrams, and a little code. By the end you'll understand why nearly every fast website on the planet leans on caching β€” and you'll be ready for the two genuinely hard problems we tackle next session.

⏱ 34 min readπŸ“– 5 topics

1 What is caching & why we do it


Explain like I'm 5

Imagine your favourite snack is kept in a big warehouse across town. Every time you're hungry, walking there and back takes ages. So you keep a few of those snacks in a bowl on your kitchen counter. Now most of the time you just grab one from the bowl β€” instant! You only make the long warehouse trip when the bowl is empty. The bowl is a cache: a small, super-close copy of stuff you keep asking for, so you don't keep making the slow trip.

A cache is a small, fast store that holds copies of data that's expensive to fetch or compute, kept closer to whoever needs it. Caching is the act of putting answers in that store the first time, then reusing them. The goal is simple and always the same: cut latency (make things feel fast) and cut load (stop hammering the slow thing behind it).

The "slow thing behind it" has a name: the origin (or source of truth) β€” the real, authoritative place the data lives, like a database or another service. A cache never replaces the origin; it just sits in front of it and answers the easy, repeated questions.

Hits, misses, and the hit ratio

Every time something asks the cache for data, exactly one of two things happens:

  • A cache hit β€” the data was already in the cache. Fast. We avoided the slow trip.
  • A cache miss β€” the data wasn't there. Now we do make the slow trip to the origin, fetch it, and usually store a copy in the cache so the next request hits.
❓
1. Ask cache
"Do you have key X?"
β†’
βœ…
2a. Hit
Return copy instantly
β†’
❌
2b. Miss
Go to origin (slow)
β†’
πŸ’Ύ
3. Store
Save copy in cache
β†’
πŸ“€
4. Return
Hand data back

The single most important number for any cache is its hit ratio (also called hit rate): the fraction of requests that were hits.

Worked example: computing a hit ratio

Suppose over one hour your cache sees 10,000 requests: 9,200 were hits and 800 were misses.

# hit ratio = hits / total requests
hit_ratio = 9200 / 10000   # = 0.92  β†’ 92%
miss_ratio = 800 / 10000   # = 0.08  β†’  8%
miss_ratio = 1 - hit_ratio  # always true

A 92% hit ratio means only 8% of requests ever bothered the slow origin. If each origin trip costs, say, 50 ms and a cache hit costs 1 ms, the average latency is:

# average = (hit% Γ— hit_cost) + (miss% Γ— miss_cost)
avg = (0.92 * 1) + (0.08 * 50)
avg = 0.92 + 4.0 = 4.92 ms   # vs 50 ms with no cache

The cache made the system roughly 10Γ— faster on average β€” just by remembering answers. Push the hit ratio higher and the average drops further.

Latency-numbers intuition: why "closer and faster" matters so much

To feel why caching pays off, you need a gut sense of how wildly different "fast" and "slow" are inside a computer. These are the famous latency numbers every engineer should know β€” rounded to make the ratios obvious, not to be exact:

OperationRough timeHuman-scale analogy (Γ—1 billion)
Read from CPU cache (L1)~1 ns1 second
Read from main memory (RAM)~100 ns~1.5 minutes
Read 1 MB from RAM~10 Β΅s~3 hours
SSD random read~100 Β΅s~1 day
Read 1 MB from SSD~1 ms~11 days
Network round trip in same datacenter~0.5 ms~6 days
Spinning-disk (HDD) seek~10 ms~4 months
Network round trip across continents~150 ms~5 years
The big idea

RAM is roughly 100,000Γ— faster than a spinning disk seek, and reaching a server in your own datacenter is hundreds of times faster than reaching one across the world. Caching is mostly about moving data up this ladder β€” out of slow disks and far-away networks, into fast memory that's physically close to the request. Every rung you climb is an order-of-magnitude win.

Two flavours of "expensive"

Data can be expensive in two ways, and caching helps with both: slow to fetch (it lives far away or on slow storage) and slow to compute (it takes heavy CPU work, like rendering a report or running a big query). Caching the result means you pay that cost once and reuse it many times.

Why caching exists at all: memory is always hierarchical

Here's the deep reason caching is unavoidable β€” it shows up even in the human brain. Think about making a cup of tea: you keep the tea, sugar, and a mug on the counter (instant), the spare packets in a cupboard (a few steps), and the bulk supply in a shop across town (a whole trip). You naturally keep what you use most in the closest, smallest spot.

Your brain does the same thing with three tiers of memory:

Brain memorySpeedCapacityDurability & access
Working memoryExtremely fastOnly 4–7 itemsVolatile, random access
Short-term memoryVery fastThousands of itemsVolatile (lasts up to weeks), random access
Long-term memoryExtremely slowPractically unlimitedPermanent, sequential access

When you sleep (during REM), your brain consolidates short-term memories into long-term storage β€” exactly like flushing a fast cache down to slow, durable storage.

Even biological memory is hierarchical!

The pattern is universal: larger storage is slower; smaller storage is faster. If you have to produce a lot of something, you use cheaper, slower technology β€” registers the size of RAM would be absurdly expensive. Larger stores are typically slow at sequential access and ultra-slow at random access; smaller stores are fast and great at random access. That mismatch is precisely why you need caching β€” to keep a small, fast copy of the hot data close by.

The same hierarchy inside a computer

Your machine stacks memory the same way, from blistering and tiny to vast and slow:

TierWhere / notesLatency
CPU registersWhat the CPU actually operates on; only a few bytes. Run at clock-tick speed (~5 billion ops/sec).~0.2 ns per access
CPU cache β€” L1, L2, L3Inside the CPU. L1 & L2 exist per core; L3 is shared across all cores.nanoseconds
Main memory (RAM)Almost 500×–1000Γ— slower than registers. Modern DDR4 random read ~10 ns.~100 ns (traditional)
HDD (magnetic disk)~100,000Γ— slower than RAM.milliseconds

So a CPU register is roughly 500Γ— faster than RAM, and RAM is roughly 100,000Γ— faster than a spinning disk. Climbing each rung of this ladder β€” disk β†’ RAM β†’ CPU cache β†’ registers β€” is an order-of-magnitude win, which is the whole reason caching pays off.

Watch out β€” caching isn't free

A cached copy can drift out of date the moment the origin changes. That trade-off between "fast" and "fresh" is the heart of caching, and it's exactly what makes the two hard problems in Topic 4 hard. Keep it in the back of your mind as we go.

Recap A cache is a small, fast, close copy of expensive data, sitting in front of the slow origin. A request is either a hit (found, fast) or a miss (not found, go to origin). The hit ratio = hits Γ· total requests, and a high one slashes average latency and origin load. It all works because moving data up the latency ladder β€” disk β†’ network β†’ RAM β€” buys order-of-magnitude speedups.

2 The caching hierarchy: layers from browser to backend


Explain like I'm 5

Think of getting a glass of water. First you check the cup already on your desk. If it's empty, you check the jug in your room. If that's empty, you go to the kitchen tap. And only if the whole house is dry do you go all the way to the well outside. Each spot is closer and faster than the next; you only travel further when the nearer spot can't help. Web requests work the same way β€” they check a chain of caches, each further from you, before finally bothering the "well" (the database).

Caching doesn't happen in just one place. A single request can pass through several caches stacked in a line, each closer to the user than the last. This stack is the caching hierarchy. The closer a layer is to the user, the faster and cheaper a hit there is β€” so we want to answer requests as early (as far "left") as possible.

The three big layers

LayerWhere it livesWhat it caches
Browser cacheOn the user's own device (in the browser)Static files already downloaded: images, CSS, JavaScript, fonts. A repeat visit can skip the network entirely.
CDN (edge cache)On servers spread around the world, physically near usersStatic assets and sometimes whole pages/API responses, served from the geographically nearest "edge."
Backend cachesInside your datacenter, in front of your database/servicesComputed results, database query results, session data β€” anything expensive to regenerate.

Two terms worth pinning down here. A CDN (Content Delivery Network) is a fleet of cache servers scattered across the globe; each location is called an edge because it sits at the edge of the network, close to users. When someone in Tokyo and someone in Berlin request the same image, each is served from a nearby edge rather than from one origin server on the other side of the planet β€” turning a ~150 ms cross-continent trip (Topic 1) into a few milliseconds. (We go much deeper on CDNs in Topic 3.)

Zooming into the browser layer (client side)

The browser cache is really several caches. Some of them a frontend developer cannot touch, and some they can:

Browser storeFrontend access?Capacity / notes
DNS cacheNoRemembers domain β†’ IP lookups (inspect at chrome://net-internals/#dns).
File / media cacheNoDownloaded images, CSS, JS, fonts, videos.
CookiesYes~4 KB β€” tiny, sent to the server on every request.
Session StorageYes~5 MB β€” cleared when the tab closes.
Local StorageYesup to ~10 MB β€” persists across sessions.
IndexedDBYesup to ~10 GB β€” a real in-browser database for large structured data.
Where a CDN actually sits

A CDN is neither client-side nor backend-side β€” it's a 3rd-party service (much like DNS). It is client-facing: only clients (browsers) talk to it; your backend servers do not. CDNs are often called the backbone of the internet, and for good reason β€” they carry roughly 70% of all internet traffic.

End-to-end: a request falling through the layers

Here's the journey of a single request when each layer in turn misses, all the way down to the origin:

πŸ§‘
User
Wants a page/asset
β†’
🌐
Browser cache
Miss β†’ ask the CDN
β†’
πŸ“
CDN edge
Miss β†’ ask origin server
β†’
πŸ–₯️
App server
Check backend cache
β†’
⚑
Backend cache
Miss β†’ query database
β†’
πŸ—„οΈ
Database
Source of truth

The data then flows back up the chain, and each layer can store a copy on the way so future requests stop earlier. The very next identical request might be answered right at the browser and never touch the network at all.

Concrete example: loading a news homepage twice

First visit (cold):

  • Browser cache: empty β†’ miss.
  • CDN edge in your city: doesn't have today's hero image yet β†’ miss β†’ fetches from origin, then keeps a copy.
  • App server: needs the "top stories" list β†’ checks its backend cache β†’ miss β†’ runs a heavy database query, then stores the result.
  • You wait maybe 800 ms. Slow, but it only happens once.

Second visit (warm), a minute later:

  • Browser cache: has the logo, CSS, and JS β†’ instant, no network.
  • CDN edge: now has the hero image cached β†’ served from your city in ~5 ms.
  • App server: "top stories" is in the backend cache β†’ hit β†’ no database query at all.
  • The page loads in ~100 ms and the database did zero work.

Same page, dramatically different experience β€” because each layer "warmed up" and now answers earlier in the chain.

Key takeaway

The further left (closer to the user) a request is answered, the faster and cheaper it is β€” and the less work every layer behind it has to do. A request only "falls through" to a deeper, slower layer when the nearer one misses. Good systems aim to answer most traffic at the browser and CDN so the precious database is bothered as little as possible.

Where this session focuses

Browser and CDN caches are governed largely by HTTP headers (we'll meet those, and more on CDNs, in Session 4: Caching 2). The rest of this session zooms into the backend caches β€” the ones you design and run inside your own system.

Recap Requests pass through a hierarchy of caches: browser (on the device) β†’ CDN/edge (near the user, worldwide) β†’ backend caches (in your datacenter) β†’ database (source of truth). A request falls deeper only on a miss, and answers flow back up, warming each layer. The goal: answer as far left as possible to save both time and load.

3 CDNs deep dive: serving the world from the edge


Explain like I'm 5

Imagine a famous movie is only available in one cinema in another country. Everyone who wants to watch it has to fly there β€” exhausting! Instead, the studio sends a copy of the film to a small cinema in every town. Now you just walk to the one near your house. A CDN is that worldwide network of nearby "cinemas" for internet content β€” pictures, videos, code β€” so you fetch them from a server near your home instead of one across the planet.

What does a CDN cache, and what problem does it solve?

A CDN caches large (> ~10 KB), mostly static data β€” content that changes rarely:

  • Multimedia: images, audio, videos, PDFs, zip files, …
  • Code: JS, HTML, CSS.
  • Essentially all user-uploaded content (images, videos, reels, avatars) is served via CDNs.

A CDN solves two problems at once: it acts as a cache and it reduces latency by serving from a server close to the user.

Why "close" matters: the US ⇄ India round trip

Imagine you're watching a Scaler lecture recording. Scaler's servers are in Mumbai (AWS South-East Asia), but you live in the US. How long does just one packet take to make the round trip?

Worked example: round-trip time from physics alone
circumference of earth  β‰ˆ 40,000 km
speed of light (vacuum) c = 299,792,458 m/s β‰ˆ 3 Γ— 10^8 m/s
but inside fiber-optic cable, light is slower β‰ˆ 2 Γ— 10^8 m/s

RTT = 40,000 km / (2 Γ— 10^8 m/s)
    = (4 Γ— 10^4 Γ— 10^3 m) / (2 Γ— 10^8 m/s)
    = 4 / 20 seconds
    = 0.2 s = 200 ms

So at least ~200 ms just for the US–India–US trip β€” and in reality, with extra hops and queuing, it's closer to 500–600 ms. That's a brutal delay before a single byte of video even arrives.

Two definitions to keep straight:

  • Latency = the delay between sending a request and receiving a response.
  • Bandwidth = bytes per second (throughput).

Fetching from far away hurts both: latency is high, and bandwidth is low too. Data crosses many hops, and your bandwidth is limited by the slowest server on the route. The intercontinental fiber-optic cables that carry trans-Atlantic and trans-Pacific traffic are a real bottleneck β€” all cross-continent traffic squeezes through them.

The fix: what if you fetched that video from a server near your home instead? Latency would drop to a few ms and bandwidth would be far higher. A server physically/geographically close to the user is called an edge node (or edge server); "the edge" simply means close to the user.

The big idea behind CDNs

If you could buy 1,000,000 servers and scatter them across the globe to act as caches, you could always serve data from one close to the user. That's exactly what a CDN does: it provides a global infrastructure of edge nodes and rents them out to anyone who wants to use it. Examples: Akamai, Fastly, Cloudflare, Cloudfront, Cloudinary, …

How do you find the nearest edge server?

MechanismHow it worksCatch
GeoDNSA special DNS that resolves the closest IP based on the user's geographic location.Low penetration β€” most DNS servers don't support GeoDNS protocols.
AnyCastA custom redirect the CDN provides to route you to the nearest server.β€”

Are CDNs databases?

No β€” a CDN is a cache, not a database

A CDN doesn't store data permanently; it temporarily holds it to serve it to clients. Any data you put on a CDN must be backed by a file-storage service like S3. And your backend servers do not read from the CDN β€” they go straight to the file storage (S3). CDNs are client-facing; only clients access them. (Nothing physically stops a backend from requesting a file from the CDN β€” it's just pointless.)

How does the CDN get the data in the first place?

Walk through an upload, using the handout's cast. Abdul uploads a video:

⬆️
1. Upload
Abdul β†’ Facebook backend
β†’
πŸ—ƒοΈ
2. Store file
Saved to S3 file storage
β†’
πŸ“‡
3. Store metadata
In a DB (Mongo / SQL)
β†’
πŸ”—
4. Register
Backend β†’ Akamai: "URL for this?"
β†’
πŸ†”
5. CDN URL
Akamai returns a CDN URL + stores the mapping

Concretely, the backend asks the CDN: "give me a CDN URL for this database URL s3.aws.com/1234/video.mp4", and the CDN returns something like cdn.akamai.com/a3b2fd5.mp4, storing the mapping CDN URL β†’ database URL internally.

Now Upinta wants to watch the video:

  • She requests the page from Facebook's backend, which returns an HTML+JS+CSS page.
  • The crucial choice is which URL the backend embeds in that page:
<html>
  <body>
    <!-- BAD: the raw database URL β€” far away, slow, high latency -->
    <video src="s3.aws.com/1234/video.mp4" />

    <!-- GOOD: the CDN URL β€” served from the nearest edge node -->
    <video src="cdn.akamai.com/a3b2fd5.mp4" />
  </body>
</html>

If the embedded URL is the database URL, Upinta (far from the DB servers) gets slow downloads and high latency. So the backend must embed the CDN URL. The edge node then:

πŸ“¨
Request
Edge gets cdn.akamai.com/…mp4
β†’
πŸ”
Cached locally?
Check edge storage
β†’
βœ…
Yes
Serve the file
β†’
⬇️
No
Look up DB URL, fetch from DB, cache it, serve

Finally, Pankaj (Upinta's neighbour) wants the same video. He hits the same edge node, but this time the file is already cached β€” because Upinta just watched it β€” so the edge simply serves it. Same edge, instant delivery.

When does the CDN actually cache the file?

The 2nd-request rule (and 1-hit wonders)

The CDN learns about a file when the backend registers it, but it does not cache it immediately. The CDN fetches from the DB for both the 1st and 2nd request (how else would it get the file?), but it only stores it locally on the 2nd request. Why? CDNs found that ~70% of all URLs are "1-hit wonders" β€” accessed once and then never again. Caching those would waste precious edge storage, so the CDN waits for a second hit as proof the file is actually popular.

War story: gaming the 2nd-request rule at Media.net

Media.net is an ad-tech platform β€” it serves ads, which are static content (images/videos) delivered via CDN, and ads must load instantly. To force the CDN to cache an ad's URL immediately (instead of waiting for real users), the team would fire two fake requests at the CDN right after registering the URL β€” triggering the 2nd-request caching rule. They'd send those fake requests from multiple countries so the file got cached at edge nodes everywhere.

Can a CDN be a bottleneck?

No

CDNs run hundreds of millions of servers distributed across the globe, so they have far more capacity than any single client demand could saturate.

Some staggering CDN stats (as of 2025)
  • Total CDN bandwidth (all CDNs combined) exceeds 10 Petabits per second β€” that's ~10 million GB/s.
  • Total CDN storage exceeds 1 Zettabyte (Giga < Tera < Peta < Exa < Zetta).
  • Akamai alone handles > 3 Exabytes per day.
  • 70% of all internet traffic is handled by CDNs.
Recap A CDN is a global fleet of edge nodes (rented out) that caches large, mostly-static content close to users β€” cutting both latency (a US⇄India trip is ~200 ms of pure physics) and bandwidth pain. You reach the nearest edge via GeoDNS or AnyCast. A CDN is a cache, not a database β€” it must be backed by storage like S3, and the backend must embed the CDN URL (not the DB URL) in its pages. The edge caches a file only on the 2nd request (because ~70% of URLs are 1-hit wonders), and CDNs are too big to ever be a bottleneck.

4 Types of backend caches: local vs distributed


Explain like I'm 5

Imagine a group of kids doing homework. Option A: each kid keeps their own little notebook of answers on their desk β€” super quick to check, but kids don't share, so they each have to figure things out themselves and might write down different answers. Option B: there's one big shared whiteboard in the middle of the room β€” everyone reads and writes the same answers, so they all agree, but you have to walk over to it each time. Backend caches come in these same two styles: your own private notebook, or one shared whiteboard.

Inside the datacenter, backend caches come in two fundamental shapes.

Local (in-process) cache β€” the private notebook

A local cache lives inside the application process itself, in that server's own memory (RAM). Because it's in-process β€” the same program, the same memory β€” a lookup is just a memory read: blisteringly fast, no network involved.

The catch: it's per-server. If you run 10 app servers behind a load balancer (recall load balancing from Session 2), you have 10 separate caches that don't know about each other. Each holds its own copy, and they can disagree.

Example: a tiny local cache in code

A bare-bones in-process cache is just a dictionary held in memory:

# A simple per-server, in-process cache
cache = {}  # lives in THIS server's RAM only

def get_user(user_id):
    if user_id in cache:        # cache hit
        return cache[user_id]
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)  # miss β†’ origin
    cache[user_id] = user        # store copy for next time
    return user

Real local caches (e.g. Caffeine in Java, or an in-memory LRU map) add size limits and expiry, but the shape is exactly this. Notice there's no network call β€” that's why local hits are measured in nanoseconds.

Global / distributed cache β€” the shared whiteboard

A global cache (also called a distributed cache) is a separate, shared service that all your app servers talk to over the network. The two famous examples are Redis and Memcached β€” fast in-memory key-value stores that run as their own servers. Every app server reads and writes the same cache, so they all see one consistent copy.

The cost: every lookup is now a network round trip (a fraction of a millisecond inside a datacenter β€” slower than local RAM, but still vastly faster than a database query or disk read). "Distributed" also hints that the cache itself can be spread across many machines β€” and the trick for spreading keys evenly across them is consistent hashing, which you met in Session 2.

πŸ–₯️
App 1
reads/writes
β†’
πŸ–₯️
App 2
reads/writes
β†’
⚑
Shared cache
Redis / Memcached β€” one copy
β†’
πŸ–₯️
App 3
reads/writes

A global cache sits as a separate layer between the app servers and the DB, and all app servers can reach it. The classic cache-aside read pattern against a Redis cache looks like this:

def get_user_preferences(request):
    user_id = request.user_id
    preferences = redisClient.getKey('pref:user_id')
    if preferences is None:                 # cache miss
        preferences = sqlClient.getPreferencesForUser(user_id)
        redisClient.setKey('pref:user_id', preferences)
    return preferences

A local cache uses the exact same logic, but the store is just an in-memory dictionary that lives in this server's RAM β€” no network call:

user_preferences_cache = {}   # stored in THIS server's RAM

def get_user_preferences(request):
    user_id = request.user_id
    preferences = user_preferences_cache.getKey('pref:user_id')
    if preferences is None:                 # cache miss
        preferences = sqlClient.getPreferencesForUser(user_id)
        user_preferences_cache.setKey('pref:user_id', preferences)
    return preferences
A local cache can make your app servers stateful

App servers are not allowed to read each other's local cache β€” it's local and private to each server. And once app servers store data, they may become stateful (depending on how the cache is used) β€” which means the application load balancer might need consistent hashing (Session 2) to keep sending the same user to the same server.

Single vs distributed cache

A quick clarification of words you'll see: a single cache means one cache node (everything fits on one machine β€” simplest, but limited by that one box's memory and a single point of failure). A distributed cache splits the data across many nodes β€” you reach for it when one cache server isn't enough, either because you need to store more data or improve read throughput.

Single vs distributed only applies to global caches

A local cache is inherently distributed β€” each app server is its own cache, and there are many app servers. So the single-vs-distributed question only makes sense for global caches. That leaves exactly three kinds of cache in practice:

  • Local cache β€” automatically distributed (one per app server).
  • Global single cache β€” e.g. a single Redis server.
  • Global distributed cache β€” e.g. a cluster of Redis servers.
Interview Q: What routing algorithm should the load balancer of a distributed cache use?

It depends on why you went distributed:

Data was too large for one server β†’ you sharded the data across cache servers, so each server holds different data. Use consistent hashing so a given key always maps to the server that holds it.

Request volume was too high for one server β†’ you replicated the data across cache servers, so each server holds the same data. Use round robin to spread load evenly.

Pros and cons, side by side

AspectLocal (in-process)Global / distributed
Speed of a hitFastest β€” memory read, no network (~ns)Fast β€” one network round trip (~sub-ms)
Consistency across serversPoor β€” each server has its own copy; they can disagreeGood β€” all servers share one copy
CapacityLimited to one server's spare RAMLarge β€” can scale out across many nodes
Survives a restart?No β€” dies with the process (must re-warm)Yes β€” separate service; data outlives app restarts
Duplication / memory useWasteful β€” same data cached N times across N serversEfficient β€” stored once, shared
Operational complexityLow β€” nothing extra to runHigher β€” another service to deploy, monitor, scale
Best forTiny, hot, read-mostly data that rarely changes (e.g. config, feature flags)Shared state that must agree everywhere (e.g. sessions, query results)
Example: when each one shines (and bites)

Local wins: a list of country codes that changes once a year. Cache it locally on every server β€” it's tiny, identical everywhere, and a network hop would be silly.

Local bites: a user's shopping cart. If server 3 caches it but the next request is load-balanced to server 7, server 7 has a stale or empty copy. Here you need a global cache so all servers see the same cart.

Many real systems use both: a small local cache in front of a global one (a "two-tier" cache), getting nanosecond hits for the hottest keys while still sharing a consistent copy for everything else.

Watch out β€” the cache stampede

Picture a popular cached item that suddenly expires. In the next instant, thousands of requests all miss at once, and all of them stampede to the database to recompute the same value simultaneously β€” potentially overwhelming it. This is a cache stampede (also called a thundering herd or dog-piling). It's a real danger with any cache, and we'll cover the standard defences (locks, request coalescing, staggered expiry) in Session 4. For now, just know that how and when things leave the cache can itself cause trouble.

Recap Backend caches are either local (in-process, per-server, fastest but inconsistent and duplicated) or global/distributed (a shared service like Redis or Memcached, consistent and scalable but a network hop away). A single cache is one node; a distributed one spreads keys across many. Pick by your needs, often combine both β€” and beware the cache stampede when popular keys expire at once.

5 The two hard problems (an introduction)


Explain like I'm 5

Your snack bowl from Topic 1 has two annoying problems. One: the bowl is small, so when it's full and you want to add a new snack, you have to throw an old one out β€” but which one? Two: the warehouse sometimes changes its snacks (new flavour!), but your bowl still has the old kind β€” so how do you know your bowl is out of date? Deciding what to throw out, and knowing when your copy is stale, are the two genuinely tricky parts of caching.

Caching sounds easy β€” keep a copy, reuse it. But two questions turn out to be deep, and they haunt every cache ever built. A famous joke captures it: "There are only two hard things in computer science: cache invalidation and naming things." Let's meet both problems so you know exactly what Session 4 is about to solve.

Both stem from the same root fact: a cache is much smaller than the database.

ChallengeWhy it happensSolutionWhen it runs
Limited spaceThe cache is small, so it can get full. To insert a new entry into a full cache, you must first evict something.Cache evictionDuring writes
Stale dataThe DB (source of truth) can be updated while the cache still holds the old value β€” that copy is now stale. You must detect it and invalidate (remove) it.Cache invalidationDuring reads (lazily)
Eviction and invalidation run together, always

Don't think of these as either/or. They solve different problems β€” eviction frees space (on writes), invalidation kills staleness (on reads) β€” and a real cache runs both algorithms simultaneously, each handling its own job. The cache is never the source of truth; the database always is.

Hard problem #1 β€” Eviction: what do we remove when the cache is full?

A cache is small on purpose (fast memory is limited and costs money). So it can't hold everything. When it's full and a new item needs space, the cache must throw something out. That decision is called eviction, and the rule it follows is an eviction policy.

Why is choosing hard? Because you're guessing the future: you want to evict the item you're least likely to need again β€” but you can't see the future, so you use clues from the past. Common policies you'll meet next session include:

PolicyThrows out…Bet it's making
LRU (Least Recently Used)the item untouched for the longest"If you haven't wanted it lately, you won't soon."
LFU (Least Frequently Used)the item asked for the fewest times"Rarely-wanted items aren't worth keeping."
FIFO (First In, First Out)the oldest-added item"Out with whatever arrived earliest."
TTL (Time To Live)anything older than a set age"After N seconds, assume it's not worth keeping."

Evict too aggressively and your hit ratio tanks (you keep throwing out things you'll need). Keep too much and you run out of memory. There's no policy that's best for every workload β€” which is exactly why it's a "hard problem."

Hard problem #2 β€” Invalidation: keeping the copy fresh

The cache holds a copy. The origin can change at any moment. The instant it does, every cached copy is potentially stale β€” out of date. Cache invalidation is the job of making sure stale copies get refreshed or removed, so users don't see old data.

This is the tug-of-war at the centre of caching:

The big idea: the fresh-vs-fast tension

A cache exists to be fast (serve a copy without checking the origin). But the only way to be perfectly fresh is to check the origin every time β€” which defeats the entire point of the cache. Every caching strategy is just a different compromise between serving stale data (fast but possibly wrong) and checking the source of truth (correct but slow).

Example: why staleness bites

An online store caches a product's price for speed. At 2:00 the price is cached as $50. At 2:05 a manager changes it to $40 in the database (the origin). But the cache still holds $50.

  • If you don't invalidate: customers keep seeing $50 β€” a real, money-losing bug, until the copy eventually expires.
  • If you invalidate too eagerly (drop the cache on every tiny change): your hit ratio collapses and the database gets hammered β€” you've half-killed the cache.

Getting this balance right β€” knowing when and how to refresh β€” is the whole art of invalidation.

A useful word: TTL does double duty

Notice TTL appeared under eviction and relates to freshness. That's no accident: putting an expiry on a cached item is the simplest invalidation strategy β€” "trust this copy for 60 seconds, then re-fetch." It's crude (data can be stale for up to 60s) but dead simple, which is why it's everywhere. Smarter strategies β€” write-through, write-back, explicit eviction on change β€” are the meat of Session 4.

Key takeaway

Eviction is about space (what to drop when full). Invalidation is about time/correctness (when a copy goes stale). They're separate problems that both come down to a guess you can't make perfectly β€” which is why even experienced engineers respect them. You now know precisely what these two problems are; in the next session we'll learn the concrete techniques that tame them.

Recap Caching's two hard problems: eviction (a full cache must drop something β€” via LRU, LFU, FIFO, TTL β€” while guessing what you won't need) and invalidation (a cached copy can go stale when the origin changes, forcing a trade-off between fast and fresh). TTL is the simplest tool for both. The detailed solutions are coming in Session 4: Caching 2.

β˜… Putting it all together


You just built the entire foundation of caching. Here's the one-paragraph story that connects all four topics:

Memory is always hierarchical β€” from your brain's working/short/long-term tiers to a computer's registers β†’ CPU cache β†’ RAM β†’ disk β€” because bigger storage is slower and smaller is faster, which is exactly why you need a cache: a small, fast copy of expensive data close to whoever needs it, turning slow trips to the origin into instant hits. The hit ratio measures how well it's working. These caches stack into a hierarchy β€” browser β†’ CDN/edge β†’ backend β†’ database β€” answering requests as far left (close to the user) as possible. CDNs are the internet's backbone: a global fleet of rented edge nodes that serve large, static content from near the user (backed by storage like S3, reached via GeoDNS/AnyCast, caching on the 2nd request). Inside the backend, caches are either local (in-process, per-server, fastest but inconsistent) or global/distributed (shared services like Redis or Memcached; shard with consistent hashing, replicate with round robin). And no matter where it lives, every cache wrestles with the two hard problems β€” eviction (what to drop when full, on writes) and invalidation (keeping copies fresh, on reads) β€” both running together, the fast-vs-fresh tension we'll learn to manage next session.

Quick self-check

A cache served 8,500 hits out of 10,000 requests. What's the hit ratio, and roughly why does it matter?

85% (8,500 Γ· 10,000). It matters because only 15% of requests reached the slow origin, so average latency and database load drop dramatically β€” the higher the hit ratio, the faster and cheaper the system.

Why is reaching RAM so much better than reaching a database on a spinning disk across the network?

RAM is roughly five-plus orders of magnitude faster than a disk seek, and a local memory read avoids the network entirely. Caching's whole job is to move hot data up that latency ladder β€” out of slow disk/far-away network into close, fast memory.

In the caching hierarchy, why do we want requests answered "as far left as possible"?

Left = closer to the user (browser, then CDN edge). A hit there is the fastest and cheapest, and it spares every deeper layer β€” CDN, backend cache, and especially the database β€” from doing any work. Requests only fall deeper on a miss.

You're caching user shopping carts behind 10 load-balanced servers. Local or global cache, and why?

Global/distributed (e.g. Redis). A local cache is per-server, so if a user's next request lands on a different server it would see a stale or empty cart. A shared cache gives all servers one consistent copy.

What's the difference between eviction and invalidation?

Eviction is about space β€” choosing what to remove when the cache is full (LRU, LFU, FIFO, TTL). Invalidation is about freshness/correctness β€” refreshing or removing a copy when the origin changes so users don't see stale data.

What is a cache stampede, and when does it strike?

When a popular cached item expires, many requests miss simultaneously and all rush to the origin to recompute the same value at once, potentially overwhelming it. It strikes right when a hot key leaves the cache; defences come in Session 4.

Roughly how long does a packet take to round-trip US ⇄ India, and why?

~200 ms from pure physics: ~40,000 km of fiber at ~2Γ—10⁸ m/s gives 40,000 km Γ· (2Γ—10⁸ m/s) = 0.2 s. In reality, with extra hops and queuing, it's closer to 500–600 ms. This is why serving from a nearby CDN edge node (a few ms) is such a huge win.

Is a CDN a database? Where must its data actually live?

No β€” a CDN is a cache, not a database. It only temporarily holds data to serve it. The data must be backed by a file-storage service like S3, and backend servers read from S3 directly (not the CDN). CDNs are client-facing.

When does a CDN edge node actually store a file in its cache, and why not on the first request?

On the 2nd request. It fetches from the DB for both the 1st and 2nd requests but only caches on the 2nd, because ~70% of URLs are "1-hit wonders" (accessed once, never again) β€” caching those would waste limited edge storage.

A distributed cache was created because the data didn't fit on one server. Which routing algorithm should its LB use?

Consistent hashing. The data was sharded, so each cache server holds different data; a given key must always route to the server that actually holds it. (If instead it were replicated to handle high request volume, you'd use round robin.)

πŸ“š References & Further Reading


Class material

Papers, docs & deep dives

CDN docs & deep dives (from the handout)

How storage & memory work (videos / extras)

  • "How do Hard Disk Drives Work?", "How do SSDs Work?", "How does Computer Memory (RAM) Work?" β€” the storage tiers behind the latency ladder.
  • "Information Storage and the Brain: Learning and Memory", "How We Make Memories (Crash Course Psychology #13)", "Tools to Enhance Working Memory & Attention" β€” the biological memory hierarchy.
  • "CppCon 2016: Timur Doumler β€” Want fast C++? Know your hardware!" β€” optimizing code for efficient CPU-cache usage.
  • "JavaScript Cookies vs Local Storage vs Session Storage" β€” the browser-side stores compared.