Expert · Performance and reliability · topic 40 of 54 · 30 min
Caching and Redis
Explanation
Why cache at all
Some answers are slow or costly to make: a database query that joins five tables, a call to another company's API, a page that needs a lot of work to build. If thousands of people ask the same question every minute, doing that work every time wastes time and money.
A cache is a place that keeps a ready copy of an answer for a short time. A cache hit means the answer was found in the cache. A cache miss means it was not, so the app does the slow work and usually saves the answer for next time.
Where caches live
| Place | What it keeps | Example |
|---|---|---|
| Browser | Files and responses the user already downloaded | Logo and CSS are not downloaded again on every page |
| CDN | Copies of files on servers close to users | Images served from a Mumbai edge server to users in Pune |
| Server memory | Values inside one running app process | A Map of settings loaded at start-up |
| Redis (shared cache) | Values shared by all your app servers | Product details used by 10 servers at once |
Cache-aside, step by step
- A request asks for product 42.
- The app builds a key, like product:42, and looks in the cache.
- Hit: return the cached copy. Done, and the database was not touched.
- Miss: read product 42 from the database.
- Save it in the cache with a TTL, for example 5 minutes.
- Return it. The next request within 5 minutes will be a hit.
- When the product changes, delete its cache key so the next read gets fresh data.
async function getProduct(id) {
const key = 'product:' + id;
const cached = await redis.get(key); // look in the cache
if (cached) return JSON.parse(cached); // hit
const product = await db.findProduct(id); // miss: ask the database
await redis.set(key, JSON.stringify(product), { EX: 300 }); // keep for 300 seconds
return product;
}
async function updatePrice(id, price) {
await db.updatePrice(id, price);
await redis.del('product:' + id); // invalidate the old copy
}TTL and invalidation
TTL means time to live: how long a cached value may be used before it expires and is thrown away. A short TTL (seconds) keeps data fresh but gives fewer hits. A long TTL (hours) gives many hits but data can be old.
Invalidation means removing or updating a cached value when the real data changes. The simple, reliable way is: write to the database first, then delete the cache key. The next read will load fresh data. There is a famous saying among programmers that cache invalidation is one of the hardest problems, because it is easy to forget one place that changes the data.
Redis in a few lines
Redis is an in-memory key-value store. In-memory means it keeps data in RAM, so reads take well under a millisecond. Key-value means you save a value under a name (the key) and get it back by that name. It also has counters, lists, sets and expiry built in. Valkey is an open-source fork of Redis (a copy that continues as its own project) with the same commands.
127.0.0.1:6379> SET product:42 "Atta 5kg, Rs 260" EX 300
OK
127.0.0.1:6379> GET product:42
"Atta 5kg, Rs 260"
127.0.0.1:6379> TTL product:42
(integer) 297
127.0.0.1:6379> INCR visits:home
(integer) 1
127.0.0.1:6379> DEL product:42
(integer) 1HTTP caching is the same idea between your server and browsers or CDNs. The Cache-Control response header says who may keep a copy and for how long, for example Cache-Control: public, max-age=86400 for a logo, or Cache-Control: no-store for a bank statement. An ETag header is a version tag: the browser asks "has it changed since this tag?" and the server can answer 304 Not Modified with no body.
Watch the lesson
Caching and Redis · 1/7
Some answers are slow to make. A cache keeps a ready copy so the next request is fast.
Prefer a video by a teacher? Find free videos on “caching explained redis cache aside pattern beginners” on YouTube ↗
In real life: A food delivery menu at lunch time
At 1 pm, thousands of people open the same popular restaurant's menu. Without a cache, each visit runs the same database query for the menu, prices and photos. With cache-aside, the first request loads the menu from the database and saves it in Redis for 60 seconds; the next thousands of requests are served from Redis. When the restaurant marks "Paneer Tikka" as sold out, the app deletes the menu key so the next visitor sees the change. The order total, though, is always calculated from the database, never from the cache.
Practice (0/3 done)
Build a TtlCache class. The constructor takes now, a function that returns the current time in milliseconds. set(key, value, ttlMs) stores a value that expires ttlMs after now(). get(key) returns the value, or undefined if it is missing or expired (expired means now() >= the expiry time).
Write getProduct(id) using cache-aside. Use the Map called cache with keys like "product:7". On a hit return the cached product. On a miss call await db.findProduct(id), save the result in the cache, and return it. The tests check that a second call for the same id does not hit the database.
For a college results website on result day, decide what to cache, where, and for how long. Also say what you will NOT cache and why.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.