Skip to content

Node.js Cluster Module Deep Dive — Complete Guide to Multi-Process Architecture

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js Cluster Module Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js cluster module creates multiple worker processes sharing the same server port, enabling multi-core utilization and horizontal scaling for HTTP applications.

What You'll Learn

By the end of this tutorial, you'll implement cluster mode with master-worker architecture, handle sticky sessions, perform zero-downtime restarts, manage shared state, and monitor worker health.

Why Cluster Matters

Node.js runs on one CPU core by default. Cluster mode uses all available cores, multiplying throughput without code changes.

Real-World Use

An Express API server on an 8-core machine forks 8 workers. Each handles requests independently. If one worker crashes, the master forks a replacement immediately.

Cluster Path

flowchart LR
  A[Child Process] --> B[Cluster Module]
  B --> C[Worker Threads]
  C --> D[PM2]
  D --> E[Scaling]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Basic Cluster Setup

Create a master Process that forks workers for each CPU core.

const cluster = require("node:cluster");
const http = require("node:http");
const os = require("node:os");
if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`Primary ${process.pid} forking ${numCPUs} workers`);
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on("exit", (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died. Forking replacement.`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end(`Worker ${process.pid} handled request\n`);
  }).listen(3000);
}

Worker Health Monitoring

Monitor worker ping/pong to detect and replace unresponsive workers.

if (cluster.isPrimary) {
  const workers = new Map();
  for (let i = 0; i < os.cpus().length; i++) {
    const worker = cluster.fork();
    workers.set(worker.id, { alive: true });
  }
  setInterval(() => {
    for (const [id, worker] of cluster.workers) {
      worker.send({ type: "ping" });
      setTimeout(() => {
        if (workers.get(worker.id)?.alive === false) {
          console.log(`Worker ${worker.id} unresponsive, killing`);
          worker.kill();
        }
        workers.set(worker.id, { alive: false });
      }, 2000);
    }
  }, 10000);
  cluster.workers.forEach((worker) => {
    worker.on("message", (msg) => {
      if (msg.type === "pong") {
        workers.set(worker.id, { alive: true });
      }
    });
  });
}

Zero-Downtime Restart

Restart workers one at a time without dropping connections.

if (cluster.isPrimary) {
  function restartWorkers() {
    const workers = Object.values(cluster.workers);
    let index = 0;
    function restartNext() {
      if (index >= workers.length) {
        console.log("All workers restarted");
        return;
      }
      const worker = workers[index];
      console.log(`Restarting worker ${worker.id}`);
      const newWorker = cluster.fork();
      newWorker.on("listening", () => {
        worker.disconnect();
        worker.kill();
        index++;
        setTimeout(restartNext, 1000);
      });
    }
    restartNext();
  }
}

Shared State Between Workers

Workers cannot share in-memory state. Use Redis or database for shared data.

const http = require("node:http");
const { createClient } = require("redis");
async function handleRequest(req, res) {
  const client = createClient({ url: process.env.REDIS_URL });
  await client.connect();
  let count = await client.get("requestCount");
  count = count ? parseInt(count) + 1 : 1;
  await client.set("requestCount", count);
  res.end(`Request #${count} handled by worker ${process.pid}`);
  await client.disconnect();
}

Graceful Shutdown

Handle SIGTERM to close connections gracefully before worker exit.

if (cluster.isWorker) {
  const server = http.createServer((req, res) => {
    res.end(`Worker ${process.pid}\n`);
  });
  server.listen(3000);
  process.on("SIGTERM", () => {
    console.log(`Worker ${process.pid} shutting down`);
    server.close(() => {
      process.exit(0);
    });
  });
}

Common Mistakes

1. Forking More Workers Than CPU Cores

Additional workers cause context switching overhead. Fork one per CPU core.

2. Not Handling Worker Exit Events

Dead workers without replacement reduce capacity. Always fork replacements on exit.

3. Assuming In-Memory State Is Shared

Each worker has its own memory. Shared state requires external storage like Redis.

4. Ignoring Sticky Sessions

Round-robin Load Balancing breaks Websocket connections. Use sticky sessions or Redis for session state.

5. No Graceful Shutdown

Killing workers immediately drops active connections. Implement graceful shutdown.

Practice Questions

1. How many workers should you fork?

One per CPU core. The primary process does not handle requests, only manages workers.

2. How do workers share the same port?

The master creates the server and passes the handle to workers. The OS distributes connections.

3. What is sticky session and why is it needed?

Ensures requests from the same client go to the same worker. Required for in-memory sessions and WebSocket.

4. How do you perform zero-downtime restart?

Fork new workers before disconnecting old ones. Wait for new workers to listen before killing old.

5. Challenge: Implement a cluster with worker health checking and auto-restart.

if (cluster.isPrimary) {
  const createWorker = () => {
    const w = cluster.fork();
    let timeout = setTimeout(() => { console.log("Worker unresponsive"); w.kill(); }, 5000);
    w.on("message", (m) => { if (m === "pong") { clearTimeout(timeout); timeout = setTimeout(() => w.kill(), 5000); }});
    w.on("exit", () => { clearTimeout(timeout); createWorker(); });
  };
  for (let i = 0; i < os.cpus().length; i++) createWorker();
}

FAQ

Is cluster mode the same as load balancing?

Similar. The OS distributes incoming connections across workers using round-robin (default on Linux) or shared socket.

Can I use cluster with Socket.IO?

Yes, but you need the Redis adapter for broadcasting across workers.

How does the primary process differ from workers?

Primary manages workers and does not handle requests. Workers execute application code and handle requests.

What happens when a worker crashes?

The primary receives the exit event and can fork a replacement worker.

Is cluster mode compatible with PM2?

PM2 provides its own cluster mode. Use either PM2 cluster or native cluster, not both.

Mini Project: Cluster Manager with Graceful Restart

Build a cluster manager with health monitoring and rolling restarts.

const cluster = require("node:cluster");
const os = require("node:os");
if (cluster.isPrimary) {
  class ClusterManager {
    constructor() { this.workers = new Map(); }
    start(count = os.cpus().length) {
      for (let i = 0; i < count; i++) this.forkWorker();
      this.startHealthCheck();
    }
    forkWorker() {
      const worker = cluster.fork();
      this.workers.set(worker.id, { worker, healthy: true });
      worker.on("message", (msg) => {
        if (msg === "healthy") this.workers.set(worker.id, { worker, healthy: true });
      });
      return worker;
    }
    startHealthCheck() {
      setInterval(() => {
        this.workers.forEach((entry, id) => {
          if (!entry.healthy) {
            console.log(`Worker ${id} unhealthy, restarting`);
            entry.worker.kill();
            this.forkWorker();
          }
          entry.healthy = false;
          entry.worker.send("health_check");
        });
      }, 10000);
    }
    rollingRestart() {
      const ids = [...this.workers.keys()];
      ids.forEach((id, i) => {
        setTimeout(() => {
          const entry = this.workers.get(id);
          if (entry) {
            entry.worker.disconnect();
            setTimeout(() => this.forkWorker(), 500);
          }
        }, i * 2000);
      });
    }
  }
  const manager = new ClusterManager();
  manager.start();
}

What's Next

Node.js Worker Threads Node.js PM2 Node.js Docker

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro