lru-cache-clustered
lru-cache-clustered puts a shared cache in the primary process of a Node.js cluster. Workers read and write named caches through an asynchronous API instead of keeping a separate full cache in every process. The underlying lru-cache engine bounds storage and evicts less recently used entries. Shared counters and coordinated fetches cover common cases such as temporary request limits and repeated database lookups. An optional local L1 cache can serve hot reads inside a worker. This is in-memory state owned by one primary, so its scope and lifetime follow that process.
How workers share one cache
Register a namespace
Import the package in the primary before forking. Processes using the same namespace must agree on primary cache options such as capacity and TTL.
Send worker operations
Worker methods send IPC requests to the primary and return Promises. Primary-process calls operate on the underlying cache directly.
Coordinate shared work
The primary serializes counters and temporary claims. fetch() coordinates concurrent misses so workers can reuse a result loaded for the same key.
Expire or evict entries
TTL and capacity rules remove cached state. Optional worker L1 copies receive asynchronous invalidations and have their own short lifetimes.
Start with the shared cache
Use Node.js 22 or newer and install the lru-cache peer alongside the scoped package. Save the following example as app.mjs and run node app.mjs. getInstance() waits for namespace registration and rejects initialization failures. Keep the returned instance for later requests in that process.
npm install @0xdoublesharp/lru-cache-clustered lru-cache@^11Read a primary-written value from a worker
The primary stores one report before starting a worker. The worker registers the same namespace, reads that report, then disconnects.
import cluster from 'node:cluster';
import { LRUCacheClustered } from '@0xdoublesharp/lru-cache-clustered';
LRUCacheClustered.bootstrap();
const cache = await LRUCacheClustered.getInstance({
namespace: 'reports', max: 1000, ttl: 60_000, failsafe: 'reject',
});
if (cluster.isPrimary) {
await cache.set('weekly', { total: 42 });
cluster.fork();
} else {
console.log(await cache.get('weekly'));
process.disconnect();
}Choose the right coordination boundary
Use shared counters when a temporary limit should apply across workers in this cluster. incr() preserves the expiration established by the first increment. Include tenant or permission context in keys when it changes the answer. Separate clusters, containers, and machines do not share this cache. Eviction or a primary restart removes claims and counters, so keep durable quotas, session records, and job-processing guarantees in an appropriate external store. Namespaces organize data; they do not restrict worker access.
Add local reads only when staleness is acceptable
L1 requires an explicit localL1 configuration with enabled and experimental set to true. Reuse one instance to retain its warm entries. Broadcast invalidation is asynchronous, so a worker can briefly read old data. Use a short local TTL, inspect localStats(), and request bypassL1 or use withoutLocal() for reads that must consult the primary.
Keep building
Find more DoubleSharp packages for application development, testing, and operations.