Node.js is known as event-driven I/O server-side JavaScript environment and single-threaded event loop based on V8 Engine ( Google’s open-source high-performance JavaScript engine) In this article, we will discuss 5 ways to make Node.JS faster and optimal in work.
With good productivity on Node.js, practically you also want how Node.js can be more optimal. In this article will discuss 5 ways to make Node.js to be faster and optimal in work.
1. Caching Your App with Redis
What is caching? On caching process, Redis as a more complex version of Memcached. Redis always served and modified data in the server’s main memory. The impact is system will quickly retrieve data that will be needed.
It also reduces time to open web pages and make your site faster. Redis works to help and improve load performance from relational databases or NoSQL by creating an excellent in-memory cache to reduce access latency.
Using Redis we can store cache using SET and GET
, besides that Redis also can work with complex type data like Lists
, Sets
, ordered data structures
, and so forth.
We will make a comparison of 2 pieces of code from Node.js. The following is when we try to retrieve data from the Google Book API, without put Redis on the endpoint.
Node.js without Redis:
And now, we will put a Redis on this endpoint. If you have trouble when using Redis, you can read the complete documentation of Redis on the Node.js here.
Node.js with Redis:
You can see, the above code explains that Redis will store the cache data of with unique key value that we have specified, using this function:
client.setex (isbn, 3600, JSON.stringify(book));
And take the cache data using the function below:
client.get(isbn, (err, result) => {
if (result) {
res.send (result);
} else {
getBook (req, res);
}
});
This is the result of testing of both codes. If we don’t use Redis as cache, it takes at least 908.545 ms
Response time without Redis
Very different, when Node.js use Redis. Look at this, it is very fast, it only takes 0.621 ms to retrieve data at the same endpoint:
Response time with Redis
2. Make sure your query is optimized
Basically, the process generated by database in the query greatly affects performance.
When end-point generates data that called by the system. A bad query, will make the process of displaying data becomes very slow.
For example, if we have a data on MongoDB, the problem is when we have 4 million rows of data, to find 1 desired data keyword, without using index schema that will be make query process very slow.
With MongoDB, we can analyze how a query process can work. You can add this query : explain(“executionStats”) to analyze and find user data from your collection.
> db.user.find({email: 'ofan@skyshi.com'}).explain("executionStats")
Look at this, before we create the index, response query when looking for data.
> db.user.find({email:'arguc.mert@gmail.com'}).explain("executionStats")
{
"queryPlanner" : ...
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"email" : {
"$eq" : "arguc.mert@gmail.com"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 0,
"totalDocsExamined" : 1039,
"executionStages" : {
...
},
...
}
},
"serverInfo" : {
...
},
"ok" : 1
}
>
From the above JSON results, there are 2 important points that we can analyze that is:
nReturned
: displays 1 to indicate that the query matches and returns 1 documents.totalDocsExamined
: MongoDB has scanned 1039 document data (this searches on all documents), to get 1 desired data.
This is when we try to add index email to the user collection.
> db.getCollection("user").createIndex({ "email": 1 }, { "name": "email_1", "unique": true })
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
The result of query analysis after add index email:
> db.user.find({email: 'arguc.mert@gmail.com'}).explain("executionStats")
{
"queryPlanner" : ...,
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"email" : 1
},
"indexName" : "email_1",
"isMultiKey" : false,
"isUnique" : true,
...
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 1,
"totalDocsExamined" : 1,
...
}
}
},
"serverInfo" : {
...
}
You can see, at the totalDocsExamined point, MongoDB only searches for 1 related data, this will make query process more faster and more efficient. Here’s the advantage when you add the index to MongoDB. By adding an index, it will also be useful when you want to sort data in a collection.
3. Check All Error Scripts with Logging
Why is logging important? The first is that you must ensure that your application program is running properly, without any fatal errors. If at any time, you find something strange in your program, it is time to find out what code makes the error. With logging, you can track activity and API process traffic.
The logging process that people often use is to use console.log (‘Log Output’) by default it will insert some logs on the output standard (stdout) and console.error (‘Log Error’) also will go in error standard (stderr). However, I recommend that you use a more popular and efficient logging module, such as Winston
, Morgan
and Buyan
.
I will give an example of how we are logging using Winston . In general, Winston has 4 custom levels that we can use such as: error
, warn
, info
, verbose
, debug
, and silly
.
Some features that can be used on Winston:
- Possible to use multiple transports of the same type
- Simple Profiling
- Supports querying of logs
- Possible to catch and log uncaughtException
- Setting the level for your logging message
First of all we need to install Winston and include Winston on the new project or that you have developed. Run the following command to do this:
npm install winston --save
This is the basic configuration Winston that we use:
const winston = require('winston');let logger = new winston.Logger({transports: [new winston.transports.File({level: 'verbose',timestamp: new Date(),filename: 'filelog-verbose.log',json: false,}),new winston.transports.File({level: 'error',timestamp: new Date(),filename: 'filelog-error.log',json: false,})]});logger.stream = {write: function(message, encoding) {logger.info(message);}};
From the code above we know that, we do use multiple transports configuration, with 2 levels logging that are verbose and error.
4. Implement HTTP/2
HTTP/2 commonly called SPDY is the latest web protocol standard developed by the IETF HTTP workgroup. HTTP/2 makes web browsing faster, easier and lower bandwidth usage. It focuses on performance, especially to solve problems that still occur in previous versions of HTTP/1.x. Currently, you can see some popular web like google, facebook and youtube, have implemented the HTTP/2 protocol on its web page.
Why is this better than HTTP/1.x?
- Multiplexing:
Multiplexing will allow multiple requests and response messages to retrieve resources in a single TCP connection simultaneously. - Header Compression:
Each request via HTTP contains header information. With HTTP/1.1, many headers are repeated in one session and duplicate the same info. This overhead is considerable, HTTP/2 removes the excess header while pressing the remaining headers and forcing all HTTP headers to be sent in a compressed format. - Server Push:
With HTTP/1.1 it must wait for the client to send the connection. Server Push allows the server to avoid delays in sending data by “pushing” responses that it claims the client needs to cache it and automatically this will speed up page load time by reducing the number of requests. - Binary Format :
HTTP / 1.1 sends data in the textual format, while HTTP/2 sends data in binary format. Binary protocols are more efficient to parse and reduce the number of errors, compared to previous versions of textual protocols.
And we know, Node.js currently supports and provides HTTP/2 implementations for core Node.js. The API is very similar to the standard node.js HTTPS API. To start with HTTP/2, you can create an HTTP/2 server on Node.js using the following code line:
To implement Transport Layer Security (TLS) and Secure Socket Layer (SSL) protocols that built on OpenSSL. You can create a self-signed SSL Certificate to generate the server.key
and server.crt
via here.
Because HTTP/2 support is still experimental, Node.js must be launched with the --expose-http2
command line :
$ node --expose-http2 node-http2.js
Now it’s time to check if a website is using the newest HTTP/2 protocol.
HTTP2 using Node.js
From the picture above, you will be able to identify that the website has been using the HTTP/2 protocol.
5. Clustering Your Node.js
By default, Node.js runs on a single thread on a single-core of the processor. and It does not utilize several cores that may be available in a system. But now, with cluster on Node.js, it allows you to easily create child processes that all share server ports. it’s meant that cluster can handle a large volume of requests with multi-core systems. And automatically, this will increase the performance of your server.
Node.js Cluster Module
Node.js has implementation the core cluster modules, that allowing applications to run on more than one core. Cluster module a parent/master process can be forked in any number of child/worker processes and communicate with them sending messages via IPC communication. Let’s see a small code snippet below :
When the node is run, there it appears that there are 4 workers currently in use on Node.js with a cluster.
Node.js cluster
Now you can visit the http:localhost:3000/cluster
link on a different tab and browser, you will find there, that each tab will have a different worker id.
Node.js clustering with PM2
PM2 is a production process manager for Node.js applications with a built-in load balancer. It allows you to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks. One of its nicer features is an automatic use of Node’s Cluster API. PM2 gives your application the ability to be run as multiple processes, without any code modifications. For example, below is a simple code built using express :
With command below, PM2 will automatically spawn as many workers as you have CPU cores. Let’s start and following commands to enable cluster using PM2:
pm2 start app.js -i 0
Now you can see, how PM2 cluster has scaled across all CPUs available
Overall, PM2 cluster is powerful tools that can really improve the concurrency and overall performance of your Node.js. This is easier than you are using an existing cluster module on Node.js Core.