You can find the Chinese version at 使用 pm2 啟動 Node.js cluster 以提升效能.
PM2 is a useful tool to manage Node.js processes, which allows us to set up the Node.js cluster to utilize the CPUs. In this post, I will introduce what PM2 is and why it is essential for Node.js applications.
The disadvantage of Node.js
Node.js implements the Asynchronous I/O using a thread pool. When a Node.js application runs, it can use multiple threads. However, the JavaScript runtime in Node.js is single-threaded and can only operate on a single CPU. As a result, some CPUs may be overloaded while others are idle at times.
The Solution: Clustering
If each Node.js process can only run on a single CPU core, why not create more Node.js processes? The problem then becomes how to make these processes listen on the same port. To solve this problem, Node.js provides an official cluster module, and the document says:
A single instance of Node.js runs in a single thread. To take advantage of multi-core systems, the user will sometimes want to launch a cluster of Node.js processes to handle the load. The cluster module allows easy creation of child processes that all share server ports.
In short, to maximize CPU usage, the cluster module allows you to create multiple child processes as workers and listen on the same port.
As shown in the picture above, the master process listens on the port and delegates the tasks to the workers. Since there are multiple workers, they can use as much CPU as possible.
PM2: The simplest way to manage Node.js processes
Even though there is a cluster module, we still need to handle many things such as auto-restart and load balancing by ourselves. But with PM2, we don't have to worry about these details.
How to install PM2
It's easy to install PM2, we can just install it globally.
npm install pm2 -g
Once PM2 is installed, we can write a basic HTTP server that listens on port 3000. When a request comes in, it will do something and respond with "Hello World". You can find the code in the Github repository.
const app = express()app.get('/', (req, res) => {
setTimeout(() => {
doSomething()
res.send('Hello World')
}, 300)
})app.listen(3000)
Instead of running node index.js
to start a single server, now we can use PM2 to run the server in cluster mode by running the command pm2 start -i 4 --name server index.js
. Here, The option -i 4
indicates that we want to create 4 worker processes.
Once the cluster is up, the output should look similar to the screenshot below. You can access the HTTP server by navigating to http://localhost:3000/ in a browser.
The process of shutting down the cluster is also simple. All you need to do is execute the command pm2 delete server
. For additional commands and further details, you can refer to the official document.
Measuring Performance
Does PM2 really enhance performance? Let's install loadtest to measure the performance. You can also try it yourself with the source code.
After installing loadtest, we will use the following command to test the server's maximum load. The option -n 1000 -c 100
specifies that there are a total of 1000 requests, with a maximum of 100 concurrent requests.
npm i loadtest -g
loadtest -n 1000 -c 100 http://localhost:3000/
Comparison of the Results
By running node index.js
to start a server instance, the average latency of all requests is 1668ms.
INFO Target URL: http://localhost:3000/
INFO Max requests: 1000
INFO Concurrency level: 100
INFO
INFO Completed requests: 1000
INFO Total time: 17.410050329 s
INFO Requests per second: 57
INFO Mean latency: 1668.1 ms
However, if we launch the cluster with pm2 start -i 4 --name server index.js
, the latency will be reduced to 929ms.
INFO Target URL: http://localhost:3000/
INFO Max requests: 1000
INFO Concurrency level: 100
INFO
INFO Completed requests: 1000
INFO Total time: 9.901042633000001 s
INFO Requests per second: 101
INFO Mean latency: 929.9 ms
According to the results, we can observe that the time required to consume 1000 requests decreased from 17.4s to 9.9s. Additionally, The requests per second are improved from 57 to 101, representing a 45% performance improvement.
Most importantly, All you need to do is use PM2 to start your server and you can get the performance improvement!
Summary
In this article, we have used PM2 to improve the performance of a Node.js server. However, Since Node.js is not good at CPU-intensive tasks such as encrypting and encoding, if the performance is still too low even after using PM2, you might want to consider rewriting the service in other languages like C++, Golang, or Rust.
This post is a brief introduction to PM2. In addition to clustering, PM2 also provides several other features. If you're interested in learning more about PM2, please refer to the official website.