Redis Caching Strategies in Code
This lesson has a runnable companion file at src/exercises/redis-caching-strategies.ts. Open it, read through each strategy, then run it against a local Redis instance to see the behavior.
npx tsx src/exercises/redis-caching-strategies.ts
What the Exercise Covers
The RedisCacheStrategy class implements five patterns:
1. Cache-Aside (Lazy Loading)
The application checks the cache, and on a miss it fetches from the database and populates the cache itself.
await cache.cacheAsideGet("user:1"); // MISS → fetches DB → fills cache
await cache.cacheAsideGet("user:1"); // HIT
2. Read-Through
Same result as cache-aside, but the cache layer is responsible for calling the database via a loader callback. The calling code never touches the DB.
await cache.readThrough("product:42", dbGet); // cache calls loader on miss
3. Write-Through
Every write goes to the cache and the database synchronously. The cache is always consistent, but writes are slower.
await cache.writeThrough("order:99", "confirmed");
// Both cache and DB are updated before this line runs
4. Write-Behind (Write-Back)
Writes go to the cache immediately. A background interval flushes queued writes to the database in batches.
cache.startWriteBehindFlush(1000);
await cache.writeBehind("metric:views", "12345");
// Cache is updated instantly; DB catches up after ~1 second
5. Refresh-Ahead
Proactively refreshes a key in the background when its TTL drops below a threshold, keeping hot data warm.
await cache.refreshAhead("session:abc", dbGet, 10);
// If TTL < 10s, triggers a non-blocking background reload
Strategy Comparison
| Strategy | Read Latency | Write Latency | Consistency | Data Loss Risk |
|---|---|---|---|---|
| Cache-Aside | Miss penalty | Low (DB only) | Eventual | None |
| Read-Through | Miss penalty | N/A | Eventual | None |
| Write-Through | Always fast | High (both) | Strong | None |
| Write-Behind | Always fast | Low (cache only) | Eventual | If cache crashes |
| Refresh-Ahead | Always fast | N/A | Eventual | None |
Things to Try
- Change the
defaultTTLto a low value (e.g. 5 seconds) and observe keys expiring - Add
console.logstatements insidedbGet/dbSetto see when the database is actually hit - Break the write-behind flush and observe that the database never gets updated
- Combine read-through with write-behind — read via
readThrough, write viawriteBehind