Other Node Performance Optimization Techniques

Learn via video courses
Topics Covered

Overview

Performance is one of the most crucial factors for any web application. Web applications with faster performance improve the user experience and lead to increased revenue from that web application.

In this article, we will look into some of the practices we should adopt while building our application to improve its performance.

Introduction

Node.js is now one of the most widely used tools to build server-side platforms on Google Chrome's JavaScript Engine (V8 Engine). It is used to develop efficient web applications. Corporate users of Node.js software include GoDaddy, IBM, LinkedIn, Microsoft, Netflix, PayPal, SAP, Walmart, Yahoo!, Amazon Web Services, etc.

The performance of the application plays a significant role in application development and maintenance. Better application performance provides the user with a nice experience and helps the product owner increase their revenue. Web application performance is determined by various factors like scalability, throughput, latency, etc. So, Let's understand briefly the ways to improve the performance of our Node.js application.

Performance Optimixzation

Profiling and Monitoring Application

Before attempting to improve the performance of the application, it's better to have a better overall performance idea. And to have a better performance idea of our node.js application, it is necessary to measure and monitor the Node.js performance of our existing Node application.

So, once you are aware of the performance of your application, you can easily optimize your web application to get maximum performance by adopting the best strategies for any inefficiencies. Following are some of the considerations the product owners must keep in mind to improve their application's performance and make the application more scalable:

  • Load testing: It is a process of evaluating the application’s load that measures the responses and usage of the application. It simulates the expected usage of a system and measures its response as the workload increases. Moreover, you can also measure the threshold load the system can take before failing(error).
  • Scalability Testing: It is a method to find the threshold point at which the application stops scaling, and it also helps in identifying the reasons behind it. Scalability testing is one of the most important tests which is helpful for product owners who want to scale their application as this testing helps identify and solve the errors and bugs that are hindrances to the web application’s scalability.
  • Stress Testing: It is developed to determine how a system performs beyond the limits of normal working conditions. Its goal is to determine how much the system can handle before it fails and how it attempts to recover from a failure.
  • Endurance Testing: This test helps you to evaluate the behavior of any application under sustained load for a long period. It also sorts memory leakage issues.
  • Volume Testing: It determines whether or not a system can cope with large amounts of data.
  • Spike Testing: It helps in the testing of an application's behavior when it is subjected to a drastic increase and decrease in load.

You must keep these above points in mind while optimizing Node.js performance. After implementing these solutions, it’s better to rerun the web application to verify the performance. Performing some or all of the above tests will provide you with several important metrics, such as response times, average latency, error rates, requests per second, throughput, CPU and memory usage, concurrent users, etc.

You can make use of Node.js application monitoring tools like PM2, Retrace, Express Status Monitor, Appmetrics, Prometheus, etc. to monitor the above-mentioned performance metrics in the node.js application.

Reducing Latency Through Caching

Let's first understand cache, A cache is a memory buffer where frequently accessed data is temporarily stored so that it can be accessed quickly. Cache serves the data faster than accessing data from the primary storage (usually a database). It is expected that data stored in a cache does not change often as it retrieves previously retrieved or computed data.

So, if you get frequent requests for some data which changes rarely in your application, then you store that data in the cache which will significantly reduce the latency of any future request. You can also make use of a cache to store the results of computationally intensive tasks, as long as it can be reused for other requests. This prevents server resources from being engaged unnecessarily by repeating the task to compute such data.

In web applications, caching is done on both sides i.e., the client side as well as the server side. Client-side caching stores the web page contents temporarily on the client side locally on the browser or a content delivery network (CDN). So, whenever the user visits that webpage again it fetches the webpage from the cache instead of re-downloading it.

Server-side caching mainly deals with the speed of data retrieval. It focuses on improving the node.js performance. Server-side caching is done either by reducing the time spent on computations required for data retrieval or by doing I/O (such as retrieving such data over the network or from a database).

You can implement caching in your Node.js application through an in-process caching solution like node-cache. It involves placing frequently accessed data in memory so that it can be accessed in less time. But there is a major problem with the in-process caching solution i.e., it is bound to a single application process and does not work well for distributed workflows (especially when caching mutable objects).

To implement caching in distributed workflows, you can use a solution like Redis or Memcached. These solutions execute independently for an application and are more practical when the application is scaled across multiple servers.

Using Timeouts when Dealing with I/O Operations

In a web application, a timeout is the maximum wait time set on a request. It represents how long a client is ready to wait for a response from the requested server. And if the client does not receive any response within the given time limit, the connection gets aborted so that the application does not hang indefinitely.

In your node.js application, your server may most likely be communicating with other external services, which may in turn be communicating with other services and so on. So, if one service in the chain is slow or unresponsive, it will slow down the entire chain which in turn will result in a slow experience for your end-users. So, it's always suggested to include the concept of timeouts in your application.

Many of the Node.js libraries available for making HTTP requests do not have a default timeout for the requests. So, you should set a timeout on the requests to prevent the application from waiting for a response indefinitely:

In the above code snippet, a timeout of 1000ms (about 1 second) or 1s is set as the default for all HTTP requests made through the axios library. The above code will ensure that any request does not take more time than the set timeout (i.e., 1s here), even if the API is unresponsive.

You can also set a timeout for any individual request when you think the global default is inappropriate for that particular request. Below is the implementation:

In the above code snippet, you can note that the timeout value of (1000ms or 1s) is a timeout set for getting requests also called read timeout. It is different from the connection timeout as connection timeout is the threshold time given to establish a TCP connection, whereas the read timeout is the threshold time given for the client to wait for a response after the connection is successfully established.

Using Load Balancer

It's always a typical challenge to create an application that can handle a huge number of incoming connections. Load balancing is a method to distribute the incoming traffic to the servers. Node.js provides you with the option to duplicate your application instance to accommodate multiple connections. And this can be accomplished with a single multicore server or several servers for which you may need to scale your Node.js application horizontally.

Since Node.js runs as single-thread programming, you can't implement a load balancer directly and take advantage of multi-core or multi-server systems. So, to implement a load balancer or to cluster the servers you need the cluster module in Node.js. The cluster module easily creates child processes (workers) and each of them runs simultaneously on their single thread thus the load is shared among the child processes (workers).

Another substitute for the Node.js native cluster module is the PM2 process manager. It is used to keep applications alive indefinitely and reload them without downtime and to facilitate common system admin tasks. PM2 has a built-in load balancer for Node.js as it includes a cluster feature that allows you to run numerous processes over all cores without any code modifications.

You can install PM2 using NPM:

Not Serving Static Assets with Node.js

Let's first understand the static assets in the Node.js application: JavaScript files, CSS files, or image files from your application are known to be as static assets. Although Node.js performs extremely well in handling dynamic content, the performance of Node.js is not so good when handling static content. So, you should use Nginx to handle static content in your application to ensure the best possible performance for your Node.js servers.

So, to improve the performance of your node.js application you should use the Nginx tool to serve static assets of your application from your servers.

Improving Throughput Using Clustering

Well, you can use the clustering technique to scale your Node.js server horizontally. Clustering of Node.js processes can be used to run multiple instances of Node.js that can distribute the workloads among their application threads. Clustering is needed in Node.js to take advantage of multi-core systems properly as the Node.js instance runs on a single thread. You can implement clustering in Node.js by Node.js cluster module. Node.js cluster module enables you to create child processes (workers) that execute simultaneously and share the same server port.

Since you are now having multiple threads to handle requests, the throughput (i.e., requests/second) of your server improves as several clients can be served concurrently. It is a common tactic to reduce downtime, slowdowns, and outages by Distributing the incoming connections across all the available worker processes reduces the downtime, slow down, and outages of the connections, and the available CPU cores are also utilized to their full potential.

Implementation of Node.js cluster module:

Output:

Utilizing Worker Threads for CPU-intensive Tasks

Any task is said to be a particular resource-intensive task if that resource is what limits the rate of progression of that task.

Examples of CPU-intensive tasks would be tasks that require heavy computations or a large number of operations, such as image processing, compression algorithms, matrix multiplication, or just really long (possibly nested) loops.

Since Node.js is single-threaded in nature, it does not perform well in executing CPU-intensive tasks. To improve the Node.js performance in these tasks, you can use Worker threads which provide a mechanism to run CPU-intensive tasks in a Node.js application without blocking the main event loop.

A worker thread is spawned by the main or parent thread and it performs its tasks independently from other workers. Unlike child processes or clusters, worker threads can share the memory by sharing SharedArrayBuffer instances or by transferring ArrayBuffer instances. And communication can also take place between worker and their parent using a message channel.

Implementation to create a worker thread using the worker_threads module:

main.js

When main.js is executed, it loads the worker.js file and passes the parameter 'n' to the function present in the worker.js file.

worker.js

In the above snippet, the getMultiplesOfFive() function is used to find all the numbers which are multiples of 5 between 0 and the specified argument which is received from the url argument from node.js i.e., http://localhost:8080/msg?n=75. The worker threads are spawned from the main thread (parent thread). And then the result is sent back to the parent by using the postMessage() method.

Additional Tips to Improve Node.js Performance

  • You should try to use asynchronous functions in your Node.js application as it helps in making the best use of the CPU.
  • It is advisable to always use the latest released stable version of Node.js in your application to get optimal performance.
  • You can implement streaming in your application for large data responses using the stream module of Node.js.

Conclusion

  • It's important to measure the current performance of the Node.js application as it helps you to know the inefficiencies in your application and then you can adopt the right strategies to optimize them.
  • Caching is a technique to store frequently accessed data in a high-speed temporary storage layer known as a cache. It is more efficient for data that does not change very often.
  • Timeouts are the maximum wait time set on any request. It needs to be included in your application so that your application does not get stuck if any external service is slow or unresponsive.
  • Through Load Balancing you can distribute the incoming traffic to the servers. Load balancing is implemented using the Node.js cluster module.
  • The performance of Node.js is not so optimal while serving static content so you can use a different tool called Nginx to serve the static contents to the application.
  • Clustering in Node.js provides multiple threads to handle the request, it increases the throughput (requests / second) of the application.
  • Worker threads provide a mechanism to run CPU-intensive tasks in a Node.js application without blocking the main event loop. To implement the worker threads mechanism in your application, you need the worker_threads module of Node.js.
  • You should use asynchronous functions and stream in your Node.js application to improve its performance and also try to have the latest stable version of Node.js for the development of your application.