cluster — Multi-core
Using all CPU cores in Node.js — the cluster module, worker processes, load balancing, and when to use cluster vs worker_threads.
Each cluster worker is a separate process with its own memory. A variable in your server code, such as a counter, a cache, a login-session map or a rate-limit table, exists once per worker, not once per app. Requests are spread across workers, so each one sees only its own copy: counts come out wrong, a user is logged in on one request and logged out on the next, and a rate limit lets through several times the intended traffic.
import cluster from "cluster";
import { createServer } from "http";
if (cluster.isPrimary) {
for (let i = 0; i < 2; i++) cluster.fork();
} else {
let visits = 0; // lives in THIS worker's memory only
createServer((req, res) => {
visits++;
res.end(`visit #${visits} (worker ${cluster.worker.id})`);
}).listen(3000);
}
// six requests in a row (which worker answers first varies):
// visit #1 (worker 2) visit #1 (worker 1) visit #2 (worker 2)
// visit #2 (worker 1) visit #3 (worker 2) visit #3 (worker 1)Using all CPU cores
Node.js uses one CPU core by default. If your server has 8 cores, 7 are idle. The cluster module creates multiple worker processes, each handling requests independently. The OS distributes incoming connections between them.
import cluster from "cluster";
import { cpus } from "os";
import { createServer } from "http";
const numCPUs = cpus().length; // 4, 8, 16 — depends on hardware
if (cluster.isPrimary) {
// This code runs in the primary (manager) process:
console.log(`Forking ${numCPUs} workers`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork(); // spawn one worker per CPU
}
cluster.on("exit", (worker, code) => {
console.log(`Worker ${worker.process.pid} died. Restarting...`);
cluster.fork(); // auto-restart crashed workers
});
} else {
// This code runs in EACH worker process:
createServer((req, res) => {
res.end(`Hello from worker ${process.pid}!`);
}).listen(3000);
// All workers share port 3000 — OS routes connections between them
}Shared state needs a single owner. In production that's an external store such as Redis or your database. Every worker reads and writes there, which is also what lets you run on more than one machine. The same principle works inside one machine with the cluster IPC channel: the primary owns the counter, and workers ask it:
import cluster from "cluster";
import { createServer } from "http";
if (cluster.isPrimary) {
let visits = 0; // one counter, owned by the primary
for (let i = 0; i < 2; i++) {
const worker = cluster.fork();
worker.on("message", (msg) => {
if (msg.type === "visit") worker.send({ type: "count", id: msg.id, visits: ++visits });
});
}
} else {
const waiting = new Map();
let nextId = 0;
process.on("message", (msg) => {
if (msg.type === "count") { waiting.get(msg.id)(msg.visits); waiting.delete(msg.id); }
});
const recordVisit = () => new Promise((resolve) => {
const id = nextId++;
waiting.set(id, resolve);
process.send({ type: "visit", id }); // ask the primary to count
});
createServer(async (req, res) => {
const visits = await recordVisit();
res.end(`visit #${visits} (worker ${cluster.worker.id})`);
}).listen(3000);
}
// visit #1 (worker 1) visit #2 (worker 2) visit #3 (worker 1)
// visit #4 (worker 2) visit #5 (worker 1) visit #6 (worker 2)Cluster vs worker_threads
Official Sources
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.