Phase 3 · Databases & Persistent DataModule 23~60 min read

Redis & Application Caching

Apply cache-aside safely with node-redis, TTLs, invalidation, stampede protection, and observable cache behavior.

What you'll learn

A cache is a deliberate consistency trade, not free speed. Implement cache-aside with explicit keys and TTLs, invalidate writes, prevent stampedes, and measure whether Redis improves the system.

By the end of this lesson, you'll be able to:

  • Implement cache-aside reads
  • Choose keys and TTLs
  • Invalidate after writes
  • Observe hits, misses, latency, and failure

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
Cache-asideThe application stores missing valuesUse when the database remains authoritative
TTLMaximum cache lifetimeBase it on acceptable staleness and recovery
StampedeMany misses recompute one valueCoalesce work or use a short expiring lock

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Define the cache boundary boundary: inputs, outputs, invariants, ownership, and expected failures.
  2. Design the data or message contract before choosing implementation details.
  3. Implement the smallest correct path with dependencies passed explicitly.
  4. Add validation, failure translation, cleanup, and concurrency behavior.
  5. Verify the boundary with realistic data and at least one adversarial case.
  6. Measure or observe the behavior before optimizing or extracting abstractions.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Read through Redis

The key is versioned, expiry explicit, and a miss falls back to the repository.

cached-tasks.js
export async function getTask(id) {
  const key = 'task:v1:' + id;
  const hit = await redis.get(key);
  if (hit) return JSON.parse(hit);
  const task = await tasks.findById(id);
  if (task) await redis.set(key, JSON.stringify(task), { EX: 60 });
  return task;
}

Invalidate after truth changes

Commit to the database first, then remove stale derived data.

update-task.js
const updated = await tasks.update(id, ownerId, patch);
await redis.del('task:v1:' + id);
return updated;

Production practice

Contract

The database owns correctness; cache failure may reduce performance but must not corrupt authoritative data.

Verification

Test hit, miss, expiry, invalidation, corrupt JSON, Redis outage, concurrent misses, and tenant key isolation.

Operations

Set memory policy and TTLs, add jitter, track hit rate and latency, bound values, and keep bypass safe.

Common failure mode

Deleting cache before a database commit lets another request repopulate the old value during the write.

Independent workshop

Add a measured Redis cache to task detail and owner summary reads.

Your finished workshop must include:

  • Versioned tenant-safe keys
  • Cache-aside helper
  • TTL with jitter
  • Post-write invalidation
  • Outage fallback
  • Metrics

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • Caches trade freshness for latency
  • Database remains authoritative
  • Every cache expires
  • Writes need invalidation
  • Measure benefit

Quick check

1. What is authoritative in cache-aside?

2. When should invalidation follow an update?

3. What reduces a stampede?

Next: Phase Project: Data-Backed Task API