Production Node.js Caching: Implementing Redis, LRU, and CDN Edge Layers
These articles are AI-generated summaries. Please check the original sources for full details.
Node.js Caching in Production: Redis, In-Memory, and CDN Edge
High-leverage caching strategies in Node.js can reduce database load by 80% and transform p99 latency from seconds to milliseconds. The AXIOM Agent highlights that while effective, poor implementation leads to thundering herds and complex cache invalidation bugs.
Why This Matters
In ideal models, caching is often viewed as a simple key-value store, but technical reality involves managing distributed state across multiple layers where network round-trips and event loop blocking become significant bottlenecks. Failing to account for distributed systems coordination can lead to split-brain scenarios and catastrophic database failures during traffic spikes.
Misconfiguring Redis can lead to O(N) blocking operations via the KEYS command, which freezes the Redis event loop. Real-world scaling requires moving beyond basic TTL-only invalidation to more robust patterns like surrogate keys and probabilistic recomputation to ensure system stability under high load.
Key Insights
- Redis is the standard distributed cache for Node.js production systems, but developers must avoid O(N) operations like redis.keys(); instead, use the SCAN command with COUNT to prevent blocking the event loop.
- In-memory LRU caching via libraries like lru-cache eliminates network round-trips entirely (< 1ms) and is ideal for per-process data such as JWT signing keys and parsed configuration.
- The stale-while-revalidate CDN directive allows serving stale responses immediately while asynchronously fetching fresh copies, effectively preventing thundering herds on public API endpoints.
- Cache Stampedes can be mitigated through the XFetch algorithm, which uses probabilistic early expiry based on recomputation costs, or via ‘Single-Flight’ promise deduplication to ensure only one upstream call occurs per cache miss.
- A three-layer cache architecture (L1: Memory, L2: Redis, L3: Database) ensures that each layer is an order of magnitude faster than the next, with L1 refreshing from L2 to maintain consistency.
Working Examples
Standard Cache-Aside pattern (Lazy Loading) using ioredis.
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
async function getUserById(userId) {
const cacheKey = `user:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await db.users.findById(userId);
if (!user) return null;
await redis.setex(cacheKey, 300, JSON.stringify(user));
return user;
}
Safe cache invalidation using SCAN to avoid blocking the Redis event loop.
async function deleteUserKeys(userId) {
const pattern = `user:${userId}*`;
let cursor = '0';
do {
const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
cursor = nextCursor;
if (keys.length > 0) await redis.del(...keys);
} while (cursor !== '0');
}
Promise deduplication (Single-Flight) to prevent multiple concurrent upstream requests for the same key.
const inflight = new Map();
async function singleFlight(key, fetchFn) {
if (inflight.has(key)) return inflight.get(key);
const promise = fetchFn().finally(() => inflight.delete(key));
inflight.set(key, promise);
return promise;
}
Practical Applications
- Use Case: High-traffic product inventory systems utilizing the Write-Through pattern to keep Redis consistent with the source of truth. Pitfall: Increased write latency since every update must commit to both the database and cache simultaneously.
- Use Case: Public API caching at the edge using Cloudflare Surrogate Keys (Cache-Tags) for selective invalidation. Pitfall: Relying solely on TTL-only invalidation, which can serve stale data longer than necessary or cause mass invalidation traffic spikes.
- Use Case: Implementing a two-layer cache (L1 LRU + L2 Redis) for feature flags and configurations. Pitfall: Setting L1 TTL longer than L2 TTL, which prevents local processes from receiving updates propagated to the distributed cache.
References:
Continue reading
Next article
REST vs GraphQL vs WebSockets vs Webhooks: A Technical Decision Guide
Related Content
Hedystia 2.3 Delivers Native Node.js Support and Universal WebSockets
Hedystia 2.3 introduces native Node.js support and a universal WebSocket package, eliminating the need for runtime-specific adapters.
Building Production-Ready Semantic Search: Implementing the Service Layer with Java and pgvector
Learn how to orchestrate semantic search pipelines using Java and Spring Boot, ensuring 100% visibility of embedding failures through a document lifecycle and robust pgvector integration.
Node.js Lifecycle Guide: Managing EOL Risks from Version 14 to 24
Node.js 20 reached EOL on April 30, 2026, leaving production environments on versions 14 through 20 without security patches or official CVE fixes.