Node.js Traps



Node.js is a very widely embraced runtime, but unfortunately it seems to be far more widely used than it is understood. This is particularly dangerous due to the nature of the system which is sigificantly different than most other platforms; the combination of the technical details of Node.js along with some practices that may be widely common in JavaScript can force the responsibility for a range of considerations that are typically mitigated elsewhere on to often unwitting developers. It additionally seems that some of the features in newer versions of ES/JavaScript further mask some of the key plumbing from its users: leading to expectations of new types of magic.

In my experience it seems as though the JavaScript programming model obscures the more foundational inner workings and chunks of the community embrace tenuous hype while ignoring fundamental concerns. Like every other technology Node.js is not a panacea and there are some things for which it is more suitable than others. Unlike many other runtimes however, Node.js is not designed for general computing purposes.

Event Loop

The most prominent concern for Node.js is that it is driven off of a single threaded event loop. This relies on a model of what is equivalent to cooperative multitasking where the sharing of resources is dependent on each unit of work yielding control of those resources rather than being preempted by some form of scheduler. Such a model has enormous benefits when each such unit of work is very small or of roughly equivalent size. This leads to the mandate of “don’t block the event loop”(1).

The idea that one should not block the event loop seems to be somewhat common knowledge, but practical application less so. I think this often comes down to a lack of understanding of just how easily blocking of the event loop can be realized. Mentioning of concepts like CPU-bound code seems to create thoughts of elaborate, estoric logic rather than something humdrum like code that takes more than constant time (i.e. serializing JSON) and how such code may compose and react as the input varies.

There may be some ways to mitigate event loop blocking but the ways in which it can manifest and cause issues such as IO polling starvation are subtle and manifold and so the most reliable and likely worthwhile approach is to keep the amount of work being done tightly bound. This is therefore the basis for the earlier statement that general compute purposes do not fit naturally within Node.js and instead “the Event Loop should orchestrate client requests, not fulfill them itself”(1).

For deployment of mid-tier systems such orchestration is likely to be a natural fit and at smaller scale an acceptable level of latency can likely be delivered through a combination of tolerating some blocking and offloading more of the work to upstream systems (such offloading is likely to present issues at large scale). Offloading to other threads with Node.js is certainly an option though seems as though it undermines the proposed value of Node.js). If using streaming and potentially some additional infrastructure Node.js may also offer a compelling filter solution. Also event loop blocking is only a practical issue if there’s something that is blocked and there are likely to be plenty of cases where that is not a concern.

As in most things the most important aspect is to maintain awareness of how this may be impacting your systems so that informed decisions can be planned and made. Keeping track of issues such as event loop lag and the time for the “tick” of an event loop cycle can help drive such awareness and make sure that information is on-hand before things catch fire. Some additional information around keeping track of this will likely be captured later.

A closely related concern that will get further attention shortly is quality of experience fairness. “The fair treatment of clients is thus the responsibility of your application”(1).

Don’t block the event loop (or the worker pool) | node.js [online]. May 2021. Available from: