Switching from cluster module to PM2 & RabbitMQ in Node.js
If you have been using Node.js for sometime, you should know that it is single threaded. This is why you can’t take full advantage of multiple core machines unless you use the cluster module or a process manager like PM2.
I’m working on an application that used the cluster module for managing processes. Although, there were some benefits to this, I decided to move from the cluster module to PM2 & RabbitMQ. This blog will cover the reasons why I made this change and provide background on how and why I moved to PM2 & RabbitMQ.
The Cluster module:
The cluster module in Node.js allows easy creation of child processes that all share server ports. I was using the cluster module to
- Run multiple processes of the app itself.
- Manage multiple
child_processesfor separate tasks like sending SMS, emails, notifications.
In the code above, there is only one master in the cluster, I get one child process for each worker:
1 smsWorker + 1 emailWorker + 1 notifWorker + numCPUs app processes.With this implementation, I had to also manage the state of the worker process. If it goes down (due to multiple reasons), I had to restart it. After adding that functionality the code looks like this:
“disconnect” event is fired whenever a worker is killed. So, then I simply start a new process
The only thing left in this implementation is the process communication between the app workers and from app workers to the dedicated task (SMS/email/notif) workers.
The cluster module provides the
process.message method to send a message to the master. With this, I was able to listen to different messages from the app and send it to the relevant worker. After adding process communication, the final code looks like this:
I have defined global methods so that I can send messages from anywhere in the app to the worker. When I call
global.emailWorker.send(“Email to send”); method from the app, it sends a message to the master process and then the master process forwards it to relevant
You’ll notice how global.smsWorker is started.
global.smsWorker = require(‘child_process’).fork(‘./smsWorker’);
smsWorker.js, emailWorker.js & notifWorker.js files are a function listening to messages on process to do relevant task.
For a more clear understanding of the flow of communication between processes, take a look at this:
This implementation works well, but there are always some pros and cons.
- Scales according to number of CPU cores available on your machine.
- Easy to manage as there is no dependency on any other module/service.
- Easy to implement process communication.
- You take a hit on performance of the app if there are too many messages.
- The implementation doesn’t appear to be the best for managing communication of dedicated workers.
- You need to manage the process state by yourself (no automation).
- You can’t start/stop/restart (sms/email/notif) workers without affecting app because they are coupled.
I had to switch to PM2 and RabbitMQ because of performance issue. As dedicated workers are coupled with the main app, I was not able to start/stop individual workers and it became difficult to maintain them separately with this implementation.
PM2, process manager for Node.js:
PM2 is a process manager for Node.js applications. It lets you efficiently manage multiple processes. It has many features including:
- Auto restart an app, if there is any change in code with Watch & Reload.
- An easy log management for processes.
- Monitoring capabilities of the process.
- An auto restart if the system reaches max memory limit or it crashes.
- Keymetrics monitoring over the web.
To configure PM2, I just added a config file at the root of the app directory.
PM2 can also take advantage of the cluster module and manage it by itself. I have configured
app process to run in
max possible processes available on the machine. This all depends on the number of cores.
As the processes are separated, now I can start/stop/restart them with
pm2 start pm2.config.js // start all processes
pm2 stop app // stop app processes
pm2 restart smsWorker // restart smsWorker
This solves the problem of managing all workers. To handle process communication, since all apps are running as a separate process, you can’t use
process.send(message); function anymore. This is why I decided to use RabbitMQ as a message broker.
RabbitMQ, message broker:
Here, instead of sending a message to process, you can publish it to RabbitMQ exchange. Workers that were subscribed to the
process.on(‘message’) now listen to RabbitMQ exchange. The smsWorker.js above now looks like this:
With the current implementation, all workers remain independent of each other and RabbitMQ takes the responsibility of communication.
- PM2 manages all processes.
- It’s easy to start/stop/restart any worker.
- Workers remain independent of each other.
- The Node.js process is free of communication messages.
- RabbitMQ is not that easy to implement in production.
- Adds dependency of PM2 and RabbitMQ modules.
- Need to manage/monitor RabbitMQ server along with PM2.
Currently in my setup, sms/email/notif workers are entirely separate. Hopefully, in the next post, I’ll write about moving them to Serverless (AWS Lambda), which is the best place for them.
If you are developing in Node.js, must check other articles on:
- How to Mock an Express Session.
- Building your first Serverless app in Node.js with AWS Lambda, S3, and API Gateway.