Clustering & Worker Threads
15 questions found
Why does Node.js provide a cluster module, given that a single Node.js process is single-threaded?
Intermediate
A single process runs JavaScript on one main thread, using only one CPU core -- the cluster module forks multiple worker processes (typically one per core), each an independent copy of the app sharing the same listening port, letting a Node.js app utilize all available cores.
const cluster = require('node:cluster');
const os = require('node:os');
if (cluster.isPrimary) {
for (let i = 0; i < os.cpus().length; i++) cluster.fork();
} else {
require('./server');
}
Real-world example
An API deployed to an 8-core server using only one core adds cluster.fork() to spawn eight workers, roughly increasing throughput eight-fold under CPU-bound load.
Common follow-ups: How does the cluster module's shared-port mechanism distribute connections across workers?;What state can be shared between cluster workers, given they're separate OS processes?
Advanced Node.js;Performance Optimization & Profiling
What happens to in-memory state (like an in-memory cache) when using the Node.js cluster module, and how do you handle this correctly?
Advanced
Each cluster worker is a separate OS process with its own memory -- an in-memory cache exists separately and inconsistently in each worker. The fix is moving shared state to an external store like Redis, accessible consistently regardless of which worker handles a given request.
// Broken with clustering: each worker has its own separate cache
const cache = new Map();
// Correct: external shared store
await redis.set(key, value);
Real-world example
A team clustering across four cores noticed roughly 1 in 4 requests seemed to miss recently cached data, traced to an in-memory Map existing separately in each worker; moving to Redis resolved it.
Common follow-ups: What's the correct way to implement rate limiting correctly across cluster workers given this same issue?;How does this apply to WebSocket connections handled by a specific worker?
Caching with Redis;WebSockets & Real-Time Communication
How does PM2's cluster mode compare to manually using Node's built-in cluster module directly in application code?
Intermediate
PM2's cluster mode achieves the same goal at the process-manager level, without needing cluster-aware application code, and additionally provides zero-downtime reloads, automatic restarts, and log aggregation that would need to be built manually with the raw cluster module.
pm2 start server.js -i max --name my-app
// server.js needs zero cluster-specific code
const app = express();
app.listen(3000);
Real-world example
A team migrates from a hand-rolled cluster.js entry point to 'pm2 start server.js -i max', immediately gaining zero-downtime reloads without maintaining that logic themselves.
Common follow-ups: What does 'zero-downtime reload' mean operationally, and how does PM2 achieve it?;When would you still want the raw cluster module instead of PM2?
Deployment & Process Managers (PM2);Advanced Node.js
How do you use SharedArrayBuffer to share memory directly between Node.js worker threads, and what are the safety considerations?
Advanced
SharedArrayBuffer allocates memory multiple worker threads can access directly without copying -- writes from one thread are immediately visible to others, requiring careful synchronization via Atomics to avoid race conditions from concurrent unsynchronized writes.
const sab = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sab);
Atomics.add(sharedArray, 0, 1);
console.log(Atomics.load(sharedArray, 0));
Real-world example
A parallel computation service divides a matrix operation across four worker threads reading/writing different slices of the same SharedArrayBuffer, avoiding the overhead of copying data via message passing.
Common follow-ups: Why must operations on shared memory use Atomics rather than regular read/write?;What are the practical limits on data types inside a SharedArrayBuffer?
Advanced Node.js;Performance Optimization & Profiling
How do you pass data between the main thread and a worker thread using postMessage(), and what are the limits on what can be sent?
Intermediate
postMessage() sends data using the structured clone algorithm, supporting most types (objects, arrays, Maps, typed arrays) but not functions or DOM nodes -- transferable objects like ArrayBuffers can be moved rather than copied for zero-copy performance, at the cost of the sender losing access afterward.
worker.postMessage({ command: 'process', data: [1, 2, 3] });
const buffer = new ArrayBuffer(1024 * 1024);
worker.postMessage({ buffer }, [buffer]); // transferred, zero-copy
Real-world example
An image-processing worker receives a large pixel buffer via a transferable ArrayBuffer rather than a structured-clone copy, avoiding duplicating tens of megabytes on every message.
Common follow-ups: What happens if you try to postMessage() a function or class instance with methods?;What's the performance difference between structured-clone copying and transferring an ArrayBuffer?
Advanced Node.js;File Uploads & Media Processing
What is a worker thread pool, and why would you build one rather than creating a new Worker for every task?
Advanced
Creating a new Worker has meaningful startup overhead -- a worker pool pre-creates a fixed number of threads once at startup and reuses them across many tasks, queuing work when all are busy, amortizing thread-creation cost across many tasks.
const pool = new Piscina({ filename: './cpu-task.js' });
app.post('/process', async (req, res) => {
const result = await pool.run(req.body);
res.json(result);
});
Real-world example
An endpoint performing CPU-intensive PDF generation uses a Piscina pool of four pre-warmed workers rather than spawning a new Worker per request, eliminating a per-request latency spike.
Common follow-ups: How do you size a worker pool relative to available CPU cores?;What happens to queued tasks if every worker is busy when a new request arrives?
Performance Optimization & Profiling;Advanced Node.js
What is the difference between the 'sticky' and default load-balancing strategies when using Node's cluster module, and when does it matter?
Intermediate
By default the primary distributes connections across workers round-robin-like. 'Sticky' load balancing ensures requests from the same client always route to the same worker -- important for WebSocket connections or scenarios relying on in-memory, per-connection state on a specific worker.
const sticky = require('sticky-session');
if (!sticky.listen(server, 3000)) {
server.once('listening', () => console.log('Server started'));
}
Real-world example
A chat app clustering across four cores discovers WebSocket reconnections sometimes landed on a different worker than the one holding session state; sticky load balancing by IP resolved dropped connections.
Common follow-ups: Why is sticky load balancing generally unnecessary if session state is already externalized to Redis?;How does sticky behavior interact with a cloud load balancer in front of the whole cluster?
WebSockets & Real-Time Communication;HTTP & HTTPS Modules
How does zero-downtime restart work with Node's cluster module or PM2, and why is it important for production deployments?
Advanced
Zero-downtime restart replaces workers one at a time -- each old worker finishes in-flight requests and stops accepting new ones while a new worker starts, so at every point some workers remain available, meaning no dropped requests during a deployment.
pm2 reload my-app
// PM2 starts a new worker, waits for it to be ready, then gracefully
// shuts down one old worker at a time until all run the new code
Real-world example
An e-commerce API deploys several times daily using 'pm2 reload' rather than 'pm2 restart', so customers mid-checkout never experience a dropped connection during routine deployments.
Common follow-ups: What's the difference between 'pm2 restart' and 'pm2 reload' regarding downtime?;How long should a worker be given to drain requests before forced termination during a reload?
Deployment & Process Managers (PM2);Docker & Containerization for Node.js
What is the difference between the number of CPU cores and the optimal number of cluster workers for a given Node.js application?
Intermediate
A common rule of thumb is one worker per CPU core, but the optimal number depends on workload -- a CPU-bound app benefits most from exactly matching cores, while a heavily I/O-bound app might benefit from slightly more workers, since extra workers can progress on other requests while some are blocked on I/O.
const numWorkers = process.env.WORKER_COUNT || os.cpus().length;
for (let i = 0; i < numWorkers; i++) cluster.fork();
Real-world example
A CPU-intensive transcoding service settles on exactly one worker per core after benchmarks showed more workers slowed things down, while a mostly I/O-bound API performs best with roughly 1.5x the core count.
Common follow-ups: How would you actually benchmark the optimal worker count for a real workload?;Does this intuition apply equally to worker threads used for CPU-bound tasks?
Performance Optimization & Profiling;Advanced Node.js
How do you handle an uncaught exception inside a specific cluster worker without taking down the entire application?
Advanced
Because each worker is an independent process, a crash doesn't directly crash the others -- but the crashed worker stops serving traffic until replaced. The primary should listen for 'exit' and fork a replacement, restoring capacity quickly while the error is still logged for investigation.
cluster.on('exit', (worker, code, signal) => {
console.error(`Worker ${worker.process.pid} died. Forking a replacement.`);
cluster.fork();
});
Real-world example
A production cluster automatically forks a replacement worker within milliseconds of any crash, keeping total capacity roughly constant while alerting the on-call engineer to investigate.
Common follow-ups: What's the risk of a worker crashing and restarting in a tight loop if a bug is triggered by every request of a certain type?;How would you add backoff or a circuit-breaker to the fork-on-exit logic?
Error Handling;Deployment & Process Managers (PM2)