How to Scale Node JS Applications

Isaac Tony Feb 02, 2024
  1. Use the Cluster Module to Scale Node JS Applications
  2. Microservices in Node JS
  3. Sharding and Partitioning in Node JS
How to Scale Node JS Applications

Scalability is an application’s ability to continue to serve users without fail, even when the workload increases. Therefore, scaling is the process of increasing compute, storage, or network resources to optimize resource utilization and meet users’ increased traffic.

Scaling can also be done to reduce failures, thus increasing the availability of the services. There are two types of scaling: scaling up, vertical scaling, and scaling out, also known as horizontal scaling.

Node JS is known to be a single-threaded language, only allowing execution of a single process at a time. However, we can utilize multiple CPU cores found in modern computers through features like the cluster module without exhausting the RAM.

In addition to this, using this module, we can easily restart our application if need be without experiencing extreme downtime. Furthermore, Node JS’s asynchronous and non-blocking nature allows us to execute multiple processes concurrently without overloading the server.

Now in Node JS, there are different strategies that we can use to scale our Node JS applications, some of which are discussed below.

Use the Cluster Module to Scale Node JS Applications

The cluster module allows us to create multi-core systems rather than relying on the single-threaded nature of Node JS.

Using this module, we can make several child processes, referred to as workers, that run on the same server port. This means that with an increase in workload, we can handle all user requests, thereby increasing the server throughput in the process.

Several child processes allow us to handle multiple requests without blocking other operations. This is mainly because every process/worker runs on its event thread and is assigned its instance in the engine and memory.

As mentioned before, in any case, if we want to restart our application or push new updates to production, making use of the cluster module allows us to reduce the downtime. This is because we can have some child processes running while we work on the rest at any point in time.

We can implement this approach by first having a master process whose primary responsibility is to handle all incoming requests. The master process will then fork the child processes and assign them a request to handle using the round-robin algorithm.

Unlike other algorithms, the round-robin does not assign roles to the child process/workers on a priority basis but rather on a numbering basis. The first request is assigned to the first available child process.

As shown here, we can create the main and child processes that share port 8080.

const cluster = require('cluster');
const cpu_cores = require('os').cpus().length;
const process = require('process');
const http = require('http');

if (cluster.isPrimary) {
  console.log(`Main Process ${process.pid} is running`);

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

  cluster.on('exit', (worker, code, signal) => {
    console.log(`This worker ${worker.process.pid} has died`);
  });
} else {
  http.createServer((req, res) => {
        res.writeHead(200);
        res.end(' This is the end\n');
      })
      .listen(8080);

  console.log(`Child Process ${process.pid} is running`);
}

Output:

Main Process 10928 is running
Child Process 13664 is running
Child Process 10396 is running
Child Process 2952 is running
Child Process 9652 is running

Microservices in Node JS

Microservices is a design pattern that allows us to break down an application into loosely coupled independent functional units that can be tested, deployed, and scaled independently.

Each functional unit or service has a dedicated database and interface. When using this architecture, the main concern is often making sure that the services are loosely coupled rather than minimizing their size.

When paired with the non-blocking nature, Node JS and microservices allow developers to decompose large enterprise applications into a modular structure. Each component can be assigned to dedicated resources and scaled only when necessary.

Besides the superior performance of using this architecture in Node JS applications, it is cost-effective and allows developers to build highly scalable applications that handle a huge workload.

Sharding and Partitioning in Node JS

These concepts are common in building scalable database architectures. Although sometimes they imply the same thing, they don’t.

Sharding enables us to achieve horizontal partitioning. When building data-intensive applications using Node JS, we can divide the data into separate server instances, thereby reducing the load on a single server.

The basis of dividing this data is based on what we refer to as a Shady key. For instance, we can decide to segment the data based on the geography of our users.

On the other hand, partitioning enables us to scale our Node JS applications using a slightly different approach. It is typical for every application to scan the requested data in the database before returning the appropriate response.

We can split large database tables into smaller tables to reduce the response time and make our Node JS application more responsive even when the workload increases. This allows our Node JS application to easily scan for data and return a response quickly.

There are far more ways that you can use to scale your Node JS applications. With the advent of more modern tools such as Amazon CloudFront, Redis, modern load balancers such as Nginx Plus or ELB by AWS, building more scalable applications is becoming easier by the day.

Author: Isaac Tony
Isaac Tony avatar Isaac Tony avatar

Isaac Tony is a professional software developer and technical writer fascinated by Tech and productivity. He helps large technical organizations communicate their message clearly through writing.

LinkedIn