Node.js Cluster Module

Node.js runs in a single thread by default, which means it uses only one CPU core even on machines with multiple cores. For production applications that receive thousands of requests per second, this is a limitation. The built-in cluster module solves this by allowing a Node.js application to create multiple worker processes — one per CPU core — that all share the same server port and handle incoming requests in parallel.

Think of it like a call center. A single call center agent (single-threaded Node.js) can only handle one call at a time. The cluster module is like hiring multiple agents — all using the same phone number (port) — so many callers can be served simultaneously.

How the Cluster Module Works

  1. The application is started as the master process.
  2. The master process forks multiple worker processes — typically one per CPU core.
  3. All worker processes run the same code and listen on the same port.
  4. The master process distributes incoming connections to workers using a round-robin algorithm.
  5. If a worker crashes, the master can detect it and spawn a replacement.

Checking the Number of CPU Cores

const os = require('os');
console.log("Available CPU cores:", os.cpus().length);

Basic Cluster Example

// cluster-app.js
const cluster = require('cluster');
const http = require('http');
const os = require('os');

const numCPUs = os.cpus().length;

if (cluster.isMaster) {
  // This block runs in the MASTER process

  console.log(`Master process PID: ${process.pid}`);
  console.log(`Forking ${numCPUs} workers...`);

  // Create one worker per CPU core
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // Listen for worker crashes and replace them
  cluster.on('exit', function(worker, code, signal) {
    console.log(`Worker ${worker.process.pid} died (code: ${code}). Restarting...`);
    cluster.fork(); // Spawn a replacement worker
  });

} else {
  // This block runs in each WORKER process

  http.createServer(function(req, res) {
    res.writeHead(200);
    res.end(`Response from Worker PID: ${process.pid}\n`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started and listening on port 3000`);
}

Run with:

node cluster-app.js

Output (on a 4-core machine):

Master process PID: 12345
Forking 4 workers...
Worker 12346 started and listening on port 3000
Worker 12347 started and listening on port 3000
Worker 12348 started and listening on port 3000
Worker 12349 started and listening on port 3000

Using the Cluster Module with Express

// express-cluster.js
const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  const numCPUs = os.cpus().length;
  console.log(`Master PID: ${process.pid} | Spawning ${numCPUs} workers`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', function(worker) {
    console.log(`Worker ${worker.process.pid} died. Forking a new one.`);
    cluster.fork();
  });

} else {
  // Worker: Start an Express server
  const express = require('express');
  const app = express();

  app.get('/', function(req, res) {
    res.json({
      message: 'Request handled by Worker',
      pid: process.pid
    });
  });

  app.listen(3000, function() {
    console.log(`Worker ${process.pid} is ready.`);
  });
}

Each time a request arrives at http://localhost:3000, it is handled by a different worker process, distributing the load.

Communication Between Master and Workers

The master and worker processes can send messages to each other using IPC (Inter-Process Communication):

if (cluster.isMaster) {
  const worker = cluster.fork();

  // Send a message to the worker
  worker.send({ task: 'process-data', payload: [1, 2, 3, 4, 5] });

  // Receive a message from the worker
  worker.on('message', function(result) {
    console.log("Master received result:", result);
  });

} else {
  process.on('message', function(data) {
    console.log("Worker received task:", data.task);

    const sum = data.payload.reduce((acc, val) => acc + val, 0);

    // Send result back to master
    process.send({ result: sum });
  });
}

Graceful Shutdown of Workers

if (cluster.isMaster) {
  const worker = cluster.fork();

  // After 5 seconds, tell the worker to stop gracefully
  setTimeout(function() {
    console.log("Sending shutdown signal to worker...");
    worker.send('shutdown');
  }, 5000);

} else {
  process.on('message', function(msg) {
    if (msg === 'shutdown') {
      console.log(`Worker ${process.pid} shutting down gracefully.`);
      process.exit(0);
    }
  });

  // Simulate the worker doing work
  setInterval(function() {
    console.log(`Worker ${process.pid} is running...`);
  }, 1000);
}

Cluster vs Child Processes vs Worker Threads

FeatureClusterChild ProcessesWorker Threads
PurposeScale a server across CPU coresRun external commands/scriptsCPU-heavy tasks in same process
Shared PortYes — all workers share the portNoNo
MemorySeparate memory per workerSeparate memoryShared memory possible
CommunicationIPC messagesIPC, stdin/stdoutMessageChannel, SharedArrayBuffer
Best ForWeb servers in productionRunning system commandsMath, parsing, encryption

PM2 – The Production-Grade Alternative

In real production environments, the cluster module is often managed using PM2 (Process Manager 2), a popular tool that handles clustering, auto-restart, logging, and monitoring with minimal configuration:

npm install -g pm2

# Start app in cluster mode using all available CPUs
pm2 start app.js -i max

# Monitor processes
pm2 monit

# List running processes
pm2 list

PM2 is the recommended approach for deploying Node.js applications in production.

Key Points

  • The cluster module allows a Node.js app to use all available CPU cores by spawning multiple worker processes.
  • The master process forks workers; all workers share the same port and handle requests independently.
  • cluster.isMaster identifies whether the current process is the master; otherwise, it is a worker.
  • Workers can be automatically restarted if they crash using the 'exit' event on the cluster.
  • The master and workers communicate through IPC using worker.send() and process.on('message').
  • PM2 is the recommended production tool for managing clustered Node.js applications.
  • Use clusters for scaling servers; use Worker Threads for CPU-intensive computations within the same application.

Leave a Comment

Your email address will not be published. Required fields are marked *