MEAN Stack – Performance Optimizations

In the past few years, MEAN stack went from ‘yet another shiny JS thingy’ to being a viable alternative to build high-performance web applications. We have quite a few medium to large apps running, that are MEAN.

One of our recent MEAN projects saw rapid growth in the user base (hundreds of thousands of users within the first few weeks), resulting in the system hitting the initially designed max-loads much earlier than anticipated. Hence we had to review the architecture and performance parameters to identify and fix the bottlenecks.

This blog is a quick rundown of the whats and hows in our initial performance tuning. Since the system was hosted in AWS, we used Amazon services wherever possible.

We implemented the following to improve the MEAN Stack project performance.

  1. NodeJS Clustering
  2. Amazon S3 as CDN
  3. Mongo replication
  4. Microservice implementation

Load Balancing

When it comes to increasing the performance of websites and web services, there are a couple of options:

  • Improve the efficiency of the code.
  • Throw more hardware at it.

Option two (throwing in more hardware) is the fastest and easiest solution to mitigate the performance woes.

Let us discuss how we can scale the Node.js server infrastructure using ELB, which stands for Elastic Load Balancing. ELB automatically distributes the incoming application traffic across a group of backend servers. The load balancer routes the incoming requests to the second server when the first server is busy processing other requests so that the end user does not experience any delay in getting the response.

  1. Application Load Balancer
  2. Network Load Balancer
  3. Classic Load Balancer

Application Load Balancer is best suited for load balancing of HTTP and HTTPS traffic and provides advanced request routing targeted at the delivery of modern application architectures, including microservices and containers. Operating at the individual request level (Layer 7), Application Load Balancer routes traffic to targets within Amazon Virtual Private Cloud (Amazon VPC) based on the content of the request. The socket forwarding is supported only in the application load balancer.  So we have used the Amazon AWS application load balancer to scale the server architecture.

The decision on how many servers to be used in the load balancer depends upon the traffic expected in the server and the number of concurrent requests the server needs to handle. Moreover, it also depends upon the system configurations in which the server is hosted such as the number of cores, memory, etc. We also need to change some web server(ex: nginx or apache) configurations as described here.

To discover the availability of your EC2 instances, a load balancer periodically sends pings, attempts connections, or sends requests to test the EC2 instances. These tests are called health checks. The status of the instances that are healthy at the time of the health check is InService. The status of any instances that are unhealthy at the time of the health check is OutOfService. The load balancer performs health checks on all registered instances, whether the instance is in a healthy or unhealthy state.

The load balancer routes request only to the healthy instances. When the load balancer determines that an instance is unhealthy, it stops routing requests to that instance. The load balancer resumes routing requests to the specific instance when it has been restored to a healthy state.

The load balancer checks the health of the registered instances using either the default health check configuration provided by Elastic Load Balancing or a health check configuration that the developer configures. In the second case, the developer has to give one health checking URL ({main domain}/health) to the IT team (who are doing the ELB implementation), which serves as the health status of the instance.

The health URL should be an API configured in Node.js program, which connects to the database and fetches some lightweight data to make sure that the instance is properly working i.e, it ensures the health of web server (Nginx), server (Node.js instance) and the database server (MongoDB). If this API could respond with the HTTP status 200 within a particular time (Say 2 seconds, which we can set in the load balancer configuration), the instance is marked as healthy, otherwise, it’s marked as unhealthy.

The load balancer periodically (Say 10 sec, which we can set) checks the health status of each instance (If we scaled to 2 servers – checks these 2 instances) through the corresponding health URL, to mark it healthy or unhealthy. So, when the next request comes, the load balancer routes the request to a healthy server instance which is available.