← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / cluster — Multi-core

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.

What it is, in plain English: Node.js runs on a single thread by default — it can only use one CPU core. The cluster module lets you spawn multiple worker processes that all share the same port. The primary process manages the workers; workers handle incoming requests. The OS distributes connections across workers (round-robin on Linux/Mac). For CPU-bound work, use worker_threads. For I/O-bound servers, use cluster.
Where this bites you

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

cluster
worker_threads
Isolation
Separate processes
Same process
Memory
Separate heaps
Shared memory possible
Crash impact
One worker only
Whole process
Port sharing
Yes — OS distributes
No
Best for
HTTP servers, I/O-bound
CPU-heavy computation
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.