A few tips to improve Nodejs performance

Sandeep
NodejsMadeEasy
Published in
6 min readFeb 25, 2020

--

Image courtesy: https://stackify.com/

In order to write performant and clean code, one should understand the working of language(or platform) and its design principles and patterns. In this post, I will list a few common points that every Nodejs developer should know which will ultimately help you write better-performing code.

There are a few things below you should learn and understand as much as you can. This will give you good knowledge of the Nodejs event loop and will help you understand this post better.

  • To know how Nodejs the Event loop works.
  • To know the differences between CPU intensive vs IO intensive tasks.
  • To understand blocking(synchronous) and non -blocking(asynchronous) code.

I am not going to explain each of the items above as they are broad topics to cover. There are others more experienced who have already explained it better. You can learn more here:

I would truly recommend going through all the above links.

Nodejs Golden Rule

Don’t block the event loop, keep it running and avoid anything that could block the thread-like synchronous network calls or infinite loops.

I believe that by reading the above posts now you understand the event loop and its core. So now we can discuss a few points where we can tune Node performance. Most of the points are centered around the event loop(Reactor pattern) only as it’s the core of Nodejs responsible for asynchronous IO.

Tip#1

Don’t read big files synchronously. Avoid it, and use streams whenever possible (Be aware of back pressuring in streams).

Tip#2

Don’t write long-running blocking code in callbacks. It will stall the event loop and prevent other code from being executed.

Tip#3

Avoid recursive calls to process.nextTick. Use setImmediate whenever possible for such use cases.

Tip#4: Complex calculations without blocking the Event Loop

Suppose you want to do complex calculations in JavaScript without blocking the Event Loop. You have two options: partitioning or offloading.

Partitioning

Consider below: Partitioned average, each of the n asynchronous steps costs O(1).

Offloading

If you need to do something more complex, partitioning is not a good option. This is because partitioning uses only the Event Loop, and you won’t benefit from multiple cores almost certainly available on your machine. Remember, the Event Loop should orchestrate client requests, not fulfill them itself. For a complicated task, move the work off of the Event Loop onto a Worker Pool. We can use child_process, cluster module or Worker threads as per the need. But there are few downsides as well. Read more here.

An Example of a worker thread

Tip#5 Utilize all Cors of CPU

Use this when worker threads are not useful for the use cases you have.

A Node.js application runs on a single thread. On multicore machines that means that the load isn’t distributed over all cores. Using the cluster module that comes with Node it’s easy to spawn a child process per CPU. Each child process maintains its own event loop and the master process transparently distributes the load between all childs.

Tip#6 Tune the Thread Pool

Libuv will create a thread pool with a size of 4. The default size of the pool can be overridden by setting the environment variable UV_THREADPOOL_SIZE. While this can solve load problems on I/O-bound applications. I’d recommend excessive load testing as a larger thread pool might still exhaust the memory or the CPU.

Tip# 7 Offload the work to Services

If Node.js spends too much time with CPU heavy operations, offloading work to services maybe even using another language that better suits a specific task might be a viable option.

Tip#8: Know, how even quick async functions can block the Event-Loop, starve I/O

Tip#9

Use lib like JSONStream instead of JSON.stringify and JSON.parse for large JSON.

Tip#10: Set NODE_ENV

Always set NODE_ENV according to your development activity(‘development’,’test’,’production’ etc.). Setting NODE_ENV to production makes your application 3 times faster

Source:https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/production/setnodeenv.md

Tip#11:

Prefer native JS methods over user-land utils like Lodash whenever possible.

Source: https://github.com/sandeepp2016/nodebestpractices/blob/master/sections/performance/nativeoverutil.md

Tip#12 Use async-await and promises wisely:

Tip#13: Go Parallel

Considering the previous point, try to do all your blocking operations — that is, requests to remote services, DB calls, and file system access — in parallel. This will reduce latency to the slowest of the blocking operations rather than the sum of each one in sequence. To keep the callbacks and error handling clean you can leverage control flow library like ‘async’ or can use built-in Promise library

Tip#14: Use binary modules

When available, use binary modules instead of JavaScript modules. For example, when you switched from a crypto-js module written in JavaScript to the compiled version that comes with Node.js, you see a big performance bump.

Tip#15: Do logging wisely

In general, there are two reasons for logging from your app: For debugging and for logging app activity (essentially, everything else). Using console.log() or console.error() to print log messages to the terminal is common practice in development. But these functions are synchronous when the destination is a terminal or a file, so they are not suitable for production unless you pipe the output to another program.

For debugging

If you’re logging for purposes of debugging, then instead of using, use a special debugging module like debug. This module enables you to use the DEBUG environment variable to control what debug messages are sent to console.error(), if any. To keep your app purely asynchronous, you’d still want to pipe console.error() to another program. But then, you’re not really going to debug in production, are you?

For app activity

If you’re logging app activity (for example, tracking traffic or API calls), instead of using console.log(), use a logging library like Winston or Bunyan. For a detailed comparison of these two libraries, see the StrongLoop blog post Comparing Winston and Bunyan Node.js Logging.

Tip#16 Use tool like Node-Clinic to detect event loop problem and Identify the bottlenecks.

courtesy: https://clinicjs.org/doctor/

See live example here https://upload.clinicjs.org/public/bfd578cbe5157c53080bea935667dc7523099050f4b0bc6f7394695021be21bd/18034.clinic-doctor.html

Summary:

Most of the things I have discussed here are centered around the event loop as it is the core of Nodejs which enables asynchronous IO. It's very important to learn and understand the Event loop. This event loop is maybe the most misunderstood concept of the platform. Most people fail their first few NodeJS apps merely due to the lack of understanding of the concepts such as the Event Loop, Error handling and asynchrony.

Operations with high complexity, large JSON parsing, applying logic over huge arrays, unsafe regex queries, and large IO operations are some of the operations that can cause the Event Loop to stall. Avoid this off-loading CPU intensive tasks to a dedicated service (e.g. job server), or breaking long tasks into small steps then using the Worker Pool are some examples of how to avoid blocking the Event Loop.

This is it for now. I hope you learned something by reading it. Feel free to drop your comments. If you like it please share it with your friends.

Further Reading

--

--