Programming
Sharing a cache between Node.js cluster workers

Node.js clusters give each worker its own memory. Put an in-memory cache in every worker and you can end up storing the same data several times on one host.
I built lru-cache-clustered to reduce that duplication. It keeps the cache in the primary process and lets workers read and write it through inter-process communication, or IPC.
Sharing the cache also gives requests a better chance of a cache hit. If one worker has already cached a result, a related request handled by another worker can reuse it. That can save a database query or avoid repeating the work that produced the value.
One cache for the cluster
The package uses lru-cache to manage the data in the primary process. LRU means least recently used. When the cache fills up, it removes the least recently used entries to make room for new ones.
Workers access the cache through a promise-based API, so a read looks like await cache.get(key). Namespaces let you keep different caches separate, each with its own size limit and expiration settings.
Try it with two workers
The example below uses the current package API and requires Node.js 22 or newer. Install the package and its lru-cache peer dependency:
npm install @0xdoublesharp/lru-cache-clustered lru-cache@^11
In this example, the primary stores a message and starts two workers. Both workers then read the same cached value. Save it as app.mjs:
import cluster from 'node:cluster';
import { LRUCacheClustered } from '@0xdoublesharp/lru-cache-clustered';
LRUCacheClustered.bootstrap();
const options = {
namespace: 'shared-example',
max: 1000,
ttl: 60_000,
failsafe: 'reject',
};
if (cluster.isPrimary) {
const cache = await LRUCacheClustered.getInstance(options);
await cache.set('message', 'Cached once, shared by both workers');
cluster.fork();
cluster.fork();
} else {
const cache = await LRUCacheClustered.getInstance(options);
const message = await cache.get('message');
console.log('Worker ' + cluster.worker.id + ': ' + message);
process.disconnect();
}
Run node app.mjs and you should see:
Worker 1: Cached once, shared by both workers
Worker 2: Cached once, shared by both workers
The workers can print in either order. Load the package in the primary before forking so its IPC listener is ready. bootstrap() makes that setup explicit, while getInstance() waits for the namespace to be registered before returning the cache.
Avoiding duplicate work
A shared cache can also help when a value has not been loaded yet. The current package's fetch() and memoize() helpers coordinate simultaneous misses for the same key. One worker loads the value while the others wait for the result, so a burst of similar requests can share the work of filling the cache.
Where it fits
This is useful when several workers in one Node.js cluster need the same temporary data. Keeping the cache inside the application means there is no separate cache service to configure or run.
IPC still adds overhead. An optional local L1 cache can avoid some IPC reads by keeping frequently used values inside each worker. That trades some memory savings for fewer messages, and those local copies can briefly be stale.
The shared cache lasts as long as the primary process. For data that must survive a restart, or state shared across separate clusters, containers, or machines, use a remote cache or database.
I originally published this as lru-cache-for-clusters-as-promised. The current package is @0xdoublesharp/lru-cache-clustered on npm, with the old name still available for existing applications. The source and more examples are on GitHub.
Project image: the lru-cache-clustered pika mascot, from the project assets, distributed with the project's MIT license.