Redis caching patterns, cache-aside, write-through, TTL strategies, and invalidation. Activate on: caching, Redis, cache invalidation, cache-aside, write-through, TTL, CDN cache, stale-while-revalidate. NOT for: CDN/reverse proxy setup (use api-gateway-reverse-proxy-expert), database query optimization (use data-warehouse-optimizer).
Design and implement caching architectures using Redis, application-level caching, and CDN layers with reliable invalidation strategies.
Activate on: "caching", "Redis cache", "cache invalidation", "cache-aside", "write-through", "TTL", "stale-while-revalidate", "cache stampede", "cache warming"
NOT for: CDN/proxy configuration → api-gateway-reverse-proxy-expert | Database query tuning → data-warehouse-optimizer | Connection pooling → database-connection-pool-manager
| Domain | Technologies |
|---|---|
| In-Memory | Redis 7.4+, Valkey, DragonflyDB, KeyDB |
| Application | Node LRU cache, Cacheable, unstorage |
| HTTP/CDN | Cache-Control, stale-while-revalidate, Surrogate-Key |
| Multi-Layer | L1 (in-process) → L2 (Redis) → L3 (CDN) |
| Invalidation | Event-driven purge, TTL, tag-based (Surrogate-Key) |
import { Redis } from 'ioredis';
const redis = new Redis();
const LOCK_TTL = 5; // seconds
async function cacheAside<T>(
key: string,
ttl: number,
fetcher: () => Promise<T>
): Promise<T> {
// Try cache first
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
// Acquire lock to prevent stampede
const lockKey = `lock:${key}`;
const acquired = await redis.set(lockKey, '1', 'EX', LOCK_TTL, 'NX');
if (!acquired) {
// Another process is fetching — wait and retry
await new Promise(r => setTimeout(r, 100));
return cacheAside(key, ttl, fetcher);
}
try {
const data = await fetcher();
await redis.set(key, JSON.stringify(data), 'EX', ttl);
return data;
} finally {
await redis.del(lockKey);
}
}
Request → L1: In-Process (LRU, 100ms TTL, ~1000 items)
│ miss
↓
L2: Redis (5-60min TTL, shared across instances)
│ miss
↓
L3: CDN (Cache-Control headers, edge-cached)
│ miss
↓
Origin (database/API)
Invalidation flows BACKWARD:
Database change → Purge L2 (Redis DEL) → L1 expires via short TTL
→ Purge L3 (Surrogate-Key purge / CDN API)
// On data change, publish invalidation event
async function updateUser(userId: string, data: UserUpdate) {
await db.users.update(userId, data);
// Invalidate all cache layers
await redis.del(`user:${userId}`);
await redis.del(`user:${userId}:profile`);
// Publish for other instances' L1 caches
await redis.publish('cache:invalidate', JSON.stringify({
pattern: `user:${userId}:*`,
timestamp: Date.now(),
}));
// CDN purge by surrogate key
await cdn.purgeTag(`user-${userId}`);
}
KEYS user:* blocks Redis; use sets to track related keys or hash structuresKEYS * or SCAN in hot paths (use sets for key tracking)