Node.js clustering made easy with PM2

Node.js clustering made easy with PM2

As you would probably know, Node.js is a platform built on Chrome's JavaScript runtime, gracefully named V8. The V8 engine, and hence Node.js, runs in a single-threaded way, therefore, doesn't take advantage of multi-core systems capabilities

As you would probably know, Node.js is a platform built on Chrome’s JavaScript runtime, gracefully named V8.
The V8 engine, and hence Node.js, runs in a single-threaded way, therefore, doesn’t take advantage of multi-core systems capabilities.

Node.js cluster module

Luckily enough, Node.js offers the cluster module, which basically will spawn some workers which can all share any TCP connection.

How does it work?

Cluster module will set up a master and then fork your server app as many times as you want it to (also called a worker).
It communicates with workers via IPC channels and comes with an embedded load-balancer which uses Round-robin algorithm to better distribute load among the workers.
When using Round-robin scheduling policy, the master accepts() all incoming connections and sends the TCP handle for that particular connection to the chosen worker (still via IPC).

How to use it?

The most basic example is the following :

const cluster = require('cluster')
const http = require('http')
const os = require('os')

const numCPUs = os.cpus().length

if (cluster.isMaster) {
  // Master:
  // Let's fork as many workers as you have CPU cores

  for (let i = 0; i < numCPUs; ++i) {
    cluster.fork()
  }
} else {
  // Worker:
  // Let's spawn a HTTP server
  // (Workers can share any TCP connection.
  //  In this case its a HTTP server)

  http.createServer((req, res) => {
    res.writeHead(200)
    res.end('hello world')
  }).listen(8080)
}

Of course, you can spawn as many workers as you wish. You’re not limited by the CPU cores number since a worker is nothing more but a child process.
As you can see, to make it work, you have to wrap your code inside some cluster handling logic and then add some more code to specify the expected behaviour in case your worker dies unexpectedly.

The PM2 way

Built-in clustering

PM2 internally handles all of the above logic for you so you don’t have to change anything in your code.
The previous code becomes:

const http = require('http')

http.createServer((req, res) => {
  res.writeHead(200)
  res.end('hello world')
}).listen(8080)

Then you type :

pm2 start app.js -i 4

-i <number of workers> will tell PM2 that you want to launch your app in cluster_mode (as opposed to fork_mode).

If ‘number of workers’ argument is 0, PM2 will automatically spawn as many workers as you have CPU cores.

┌──────┬────┬─────────┬─────────┬────────┬─────┬────────┬───────────┐
│ Name │ id │ version | mode    │ status │ ↺   │ cpu    │ memory    │
├──────┼────┼─────────┼─────────┼────────┼─────┼────────┼───────────┤
│ app  │ 0  │ 1.0.0   │ cluster │ online │ 0   │ 0%     │ 42.7 MB   │
│ app  │ 1  │ 1.0.0   │ cluster │ online │ 0   │ 0%     │ 42.8 MB   │
│ app  │ 2  │ 1.0.0   │ cluster │ online │ 0   │ 0.2%   │ 43.4 MB   │
│ app  │ 3  │ 1.0.0   │ cluster │ online │ 0   │ 0%     │ 43.2 MB   │
│ app  │ 4  │ 1.0.0   │ cluster │ online │ 0   │ 0.2%   │ 43.0 MB   │
│ app  │ 5  │ 1.0.0   │ cluster │ online │ 0   │ 0%     │ 43.2 MB   │
│ app  │ 6  │ 1.0.0   │ cluster │ online │ 0   │ 0%     │ 43.0 MB   │
│ app  │ 7  │ 1.0.0   │ cluster │ online │ 0   │ 0.2%   │ 43.3 MB   │
└──────┴────┴─────────┴─────────┴────────┴─────┴────────┴───────────┘
 Use `pm2 show <id|name>` to get more details about an app

Keeping your apps running no matter what

If any of your workers happens to die, PM2 will restart them immediatly so you don’t have to worry about that either.
Or, of course, you can, at any time, restart them manually as follows:

pm2 restart app

Scaling your cluster in realtime

If you consider that you don’t have enough workers or more than needed, you can scale your cluster anytime by hitting pm2 scale <app name> <n> where <n> can be a consistent number which the cluster will scale up or down to.
It can also be an addition such as pm2 scale app +3 in which case 3 more workers will be added to the cluster.

Updating your apps in production with zero downtime

PM2 reload <app name> feature will restart your workers one by one, and for each worker, wait till the new one has spawned before killing the old one.
This way, your server keeps running even when you are deploying the new patch straight to production.

You can also use gracefulReload feature which does pretty much the same thing but instead of immediatly killing the worker it will send it a shutdown signal via IPC so it can close ongoing connections or perform some custom tasks before exiting gracefully.
Example :

process.on('message', msg => {
  if (msg === 'shutdown') {
    close_all_connections()
    delete_cache()
    server.close()
    process.exit(0)
  }
})

Conclusion

Cluster module is a powerful tool. It gets even better and easy to use along with PM2.
Cluster.js was experimental on Node 0.10.x and is considered to be mature and production-ready since Node 0.11.x latest releases and of course Node 0.12.x.
We strongly suggest you to always use the latest version of Node.js and PM2 since both of these projects’ contributors are working hard every day to make them better.

Enjoy Node.js’ clustering with PM2!

With PM2 Harden your Node.js Workload and be Ready to Scale

Setup takes 2 minutes with no configuration change