How to Scale a Nodejs Server : Vertical Scaling

Prerequisites

  • Basic understanding of JavaScript

  • Know how to create a basic express server

Introduction

In this article, we will discuss how to do vertical scaling in NodeJS. But first, we need to understand what scaling means to grasp the concept of vertical scaling.

Scaling a server

Scaling the server is a strategy to handle increased load and ensure your server can manage higher traffic volumes efficiently. This involves optimizing resources such as CPU, memory, and storage to enhance the server's performance and reliability under heavy usage.

Understanding Vertical Scaling

Vertical scaling, also known as "scaling up," refers to the process of increasing the resources (CPU, RAM, storage, etc.) of a single server or node to handle higher loads and more complex tasks. This approach is often simpler to implement compared to horizontal scaling, which involves adding more servers or nodes to distribute the workload.

Why Nodejs does not support Vertical scaling

As shown in the diagram above NodeJS never utilize the full resources of your CPUs. So even if you think that by just scaling up the size of a server for example : Scaling your AWS ec2 instance from t3-large → T3-xlarge will only increase your server bill not the performance of your application Because of the bellow mentioned reasons.

Single-Threaded Event Loop

Node.js is single-threaded by default and uses an event-driven, non-blocking I/O model. This means it runs on a single thread and relies on callbacks to handle concurrent requests

Limited Memory Usage

By default, Node.js limits itself to using 1.76GB of memory on 64-bit machines. Even if you have a server with 32GB of RAM, the Node process will only consume a fraction of it


NodeJS provide an inbuild module to handle Multi-threading named Cluster

Example

Express code that we want to run on multiple threads

const express = require("express");
const PORT = 3000;
const app = express();

app.get("/", (req, res) => {
  // Some Task
  let count = 0;
  while (count < 1000000000) {
    count++;
  }
  return res.status(200).json({
    message: `Hello from the express Server ${process.pid} final count is ${count}`,
  });
});

app.listen(PORT, () => {
  console.log(`server is running at ${PORT}`);
});

In order to run your code on multiple thread you need to know how many cores you have in you computer

const os = require("os");
const NumberOfCpu = os.cpus().length;

Now we can run our code on multiple threads using the cluster module. cluster.isPrimary tells us whether we are on the default thread or not. When we are on the primary thread, we will create more threads. If we are on a non-primary thread, we will run our Express server code.

const express = require("express");
const cluster = require("cluster");
const os from "os";

const NumberOfCpu = os.cpus().length;

if (cluster.isPrimary) {
  console.log(`Number of CPUs is ${totalCPUs}`);
  console.log(`Main ${process.pid} is running`);

  // Forking new worker threads.
  for (let i = 0; i < NumberOfCpu; i++) {
    cluster.fork();
  }
// if threads one stop working this code will run new thread
  cluster.on("exit", (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} terminated`);
    console.log("Let's fork another worker!");
    cluster.fork();
  });
} else {
// Your Express Server Code
}

Full Example Code

import express from "express";
import cluster from "cluster";
import os from "os";

const NumberOfCpu = os.cpus().length;

if (cluster.isPrimary) {
  console.log(`Number of CPUs is ${NumberOfCpu}`);
  console.log(`Main ${process.pid} is running`);

  // Forking new worker threads.
  for (let i = 0; i < NumberOfCpu; i++) {
    cluster.fork();
  }
// if threads one stop working this code will run new thread
  cluster.on("exit", (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} terminated`);
    console.log("Let's fork another worker!");
    cluster.fork();
  });
} else {
const PORT = 3000;
const app = express();

app.get("/", (req, res) => {
  // Some Task
  let count = 0;
  while (count < 1000000000) {
    count++;
  }
  return res.status(200).json({
    message: `Hello from the express Server ${process.pid} final count is ${count}`,
  });
});

app.listen(PORT, () => {
  console.log(`server is running at ${PORT} and thread ${process.pid}`);
});
}