Redis and the Cache-Aside Pattern
Objectives
By the end of this lesson, you should be able to:
- Explain what Redis is, and why it’s commonly used as a cache
- Implement the cache-aside pattern, checking a cache before falling back to the real source
- Confirm a cached read is genuinely faster than an uncached one
💡 Why this matters: Lesson 1 measured the cost of skipping caching entirely. This lesson builds the actual mechanism, Redis, and the single most common caching pattern, checking the cache first, only falling back to the expensive operation on a miss.
⚠️ A note on verification: a real Redis server can’t run in this course’s own sandboxed tooling, no root access to install or run system services. The code below uses
ioredis-mock, an in-memory library implementing Redis’s real client API and command behavior, so the caching logic itself, cache hits, misses, and TTL expiration, is genuinely tested, just against an in-memory stand-in for a Redis server rather than a live one. The same code runs unchanged against a real Redis instance, only the connection details differ.
What Redis Is
Redis is an in-memory data store, exceptionally fast because it keeps data in memory rather than on disk, commonly used as a cache, a session store (an alternative to express-session’s default in-memory store, Course 3, Module 2), and a message broker, this module focuses specifically on its use as a cache, sitting in front of a slower database.
The Cache-Aside Pattern
const Redis = require('ioredis'); // 'ioredis-mock' in this course's own sandboxed tooling
const redis = new Redis();
const { fetchUserFromDatabase } = require('./slowdb');
async function getUser(id) {
const cacheKey = `user:${id}`;
const cached = await redis.get(cacheKey);
if (cached) {
return { data: JSON.parse(cached), source: 'cache' };
}
const user = await fetchUserFromDatabase(id);
await redis.set(cacheKey, JSON.stringify(user), 'EX', 60);
return { data: user, source: 'database' };
}
Check the cache first (redis.get), on a hit, return immediately, on a miss, fall back to the real, expensive source, then store the result in the cache (redis.set, with 'EX', 60, a 60-second expiration) before returning it, this is cache-aside, the application itself manages the cache, deciding when to read from it and when to populate it, Redis doesn’t do this automatically on its own.
Confirming It Actually Works
const start1 = Date.now();
const first = await getUser(1);
console.log(`First call: ${Date.now() - start1}ms, source: ${first.source}`);
const start2 = Date.now();
const second = await getUser(1);
console.log(`Second call: ${Date.now() - start2}ms, source: ${second.source}`);
First call: 202ms, source: database
Second call: 0ms, source: cache
Third call: 0ms, source: cache
The first call pays Lesson 1’s full 200ms cost, and populates the cache, every call after that, for the same key, returns instantly, straight from Redis, no database access at all, this is the entire benefit of caching, made concrete and measured, not just described.
Why JSON.stringify and JSON.parse
Redis stores strings (and a few other simple types), not arbitrary JavaScript objects directly, JSON.stringify before set, JSON.parse after get is the standard way to store structured data in Redis, this round-trip is cheap, nowhere close to the cost of the original expensive operation being avoided.
Try It
- Build
getUserandfetchUserFromDatabase, and confirm the first call is slow and every call after it, for the same id, is fast. - Call
getUserfor a second, different id, and confirm it’s slow again, the first time, since it’s a different cache key entirely. - Explain, in your own words, why
redis.set(..., 'EX', 60)includes an expiration, rather than caching the value forever. - Explain, in one or two sentences, why the application code itself decides when to check and populate the cache, rather than Redis doing this automatically.
Recap
- Redis is an in-memory data store, fast enough to make caching genuinely worthwhile, commonly used specifically as an application cache.
- Cache-aside means checking the cache first, falling back to the real source on a miss, then populating the cache, application code manages this explicitly.
- A cached read confirmed here, instant, versus 200ms for the first, uncached one, is the entire, measurable benefit caching provides.
Next lesson: cache invalidation, keeping cached data from becoming actively wrong once the real data changes.