CPU Intensive Node.js: Part 1
We explore the issues and solutions around running CPU intensive code in Node.js; in particular in a web server.

Node.js’ single-threaded approach to running user code (the code that you write) can pose a problem when running CPU intensive code.
Problem
We first illustrate the problem of running CPU intensive code in a typical Node.js web application. In this case, we modify Node.js’ hello world example using the sleep package to simulate a web server where each request takes five seconds running CPU intensive code (complete example available).
index.js
/* eslint-disable no-console */
const http = require('http');
const { sleep } = require('sleep');const hostname = '127.0.0.1';
const port = 3000;const server = http.createServer((req, res) => {
sleep(5); // ARTIFICIAL CPU INTENSIVE
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
The reason that this is problematic is that each request blocks the JavaScript event loop for five seconds. For example, say two requests come in roughly at the same time. The first request gets immediately handled and completes in the expected five seconds. The second request, however, gets stuck in the event queue for five seconds and then completes five seconds later (takes ten seconds).
This example experiences performance issues once we than more than one request in a five second period; pretty restrictive.
Worse
To further illustrate the problem of blocking the JavaScript event loop, we can create a second web application example based on the Express hello world example. In this case, we have two endpoints one that is not CPU intensive and one that is (complete example available).
index.js
/* eslint-disable no-console */
const express = require('express');
const { sleep } = require('sleep');const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.get('/intense', (req, res) => {
sleep(5); // ARTIFICIAL CPU INTENSIVE
res.send('Hello Intense!');
});
app.listen(3000, () => console.log('Example app listening on port 3000!'));
Like the previous example, this example experiences performance issues (for five seconds) once we have a request to the intense endpoint. In this case, all requests to all endpoints are delayed until the CPU intensive code finishes; yuck!
Fork
Similar to other programming languages, Node.js can fork new child processes to handle intensive CPU tasks without blocking the parent process’s event loop. Maybe we can use this approach in rewriting our web application (full example available).
index.js
/* eslint-disable no-console */
const express = require('express');
const { fork } = require('child_process');const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.get('/intense', (req, res) => {
const worker = fork('./worker');
worker.on('message', ({ fruit }) => {
res.send(`Hello Intense ${fruit}!`);
worker.kill();
});
worker.send({ letter: 'a' });
});
app.listen(3000, () => console.log('Example app listening on port 3000!'));
worker.js
const { sleep } = require('sleep');process.on('message', ({ letter }) => {
sleep(5); // ARTIFICIAL CPU INTENSIVE
let fruit = null;
switch (letter) {
case 'a':
fruit = 'apple';
break;
default:
fruit = 'unknown';
}
process.send({ fruit });
});
This all looks good until you dig a little deeper.
It is important to keep in mind that spawned Node.js child processes are independent of the parent with exception of the IPC communication channel that is established between the two. Each process has its own memory, with their own V8 instances. Because of the additional resource allocations required, spawning a large number of child Node.js processes is not recommended.
— Node.js Team
Looking at my OS’s process monitor, I observed that the parent process consumed around 14MiB of memory and each child process (while running) consumed around 7MiB of memory. With my machine showing it has around 1740MiB of free memory, around 250 simultaneous calls to the CPU intensive endpoint would crash it.
note: As comparison, the Go language with goroutines would only uses several KiB per call (thus could handle 250,000 simultaneous calls).
This solution does not scale well for a Node.js web server; yuck.
Next Steps
There is a common solution to this sort of Node.js problem (web server with CPU intensive endpoints): setting up a queue and a pool of worker processes. In the next article, CPU Intensive Node.js: Part 2 we explore this solution.
✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.