Node.js v13.10.0 introduced a built-in CLS API, namely, new class AsyncLocalStorage located in the well-known experimental async_hooks module. In this short post I’ll try to explain why it is so important.

CLS API 101

CLS stands for Continuation-Local Storage, an async variation of the Thread-Local Storage concept from the multithreaded world. CLS APIs allow you to associate and track contexts through asynchronous execution chains. Without such API, you have to either pass the context object explicitly (which sometimes is not an option), or deal with lots of monkey-patching for all async APIs in Node.js.

CLS APIs are often used in Application Performance Monitoring (APM) tools for Node.js available in various cloud platforms. But there are situations when you may want to use CLS in your app directly or transitively, i.e. it may be used in your dependencies. Request id tracking is one of such use cases. You may read more on this topic in my previous blog post.

But let’s return to AsyncLocalStorage . To give you an impression of the API, here is an example that shows how to use AsyncLocalStorage to build a primitive logger with request id tracing capabilities.

Why AsyncLocalStorage matters?

Now, when you have a better understanding of the subject, let’s go through a list of advantages of the new API.

There are several Node.js user-land modules that implement CLS, the most popular among which are continuation-local-storage and its successor cls-hooked . Unfortunately, none of them is widely adopted among the community and many Node.js libraries do not play well with these modules, leading to various context loss issues. Being a part of the standard library, AsyncLocalStorage has all chances to change that, as library maintainers will much more likely fix an issue related with a core API, than a 3-rd party one. AsyncLocalStorage provides a simple, high-level API, which hides the complexity and low-level details of async hooks. Thus, it is more stable (in terms of compatibility) than APIs it builds upon and, potentially, may be moved into a separate, stable module. Performance-wise AsyncLocalStorage is significantly faster than its user-land competitors (see the next section for benchmark results). Mainly, that’s because it avoids destroy hook usage (thanks to executionAsyncResource ).

Performance Comparison

Disclaimer. This comparison is intentionally kept as simple as possible, i.e. it only considers mean results of benchmark run series collected on a dev machine.

As you may already know, Node.js core includes a set of various benchmarks maintained by the core team. One of those benchmarks (namely, async-resource-vs-destroy.js with necessary modifications) was used to compare AsyncLocalStorage with alternatives. This benchmark is aimed to simulate a more or less standard web app. To do that, it starts an HTTP server which schedules a setTimeout and then reads a file (all in the same CLS context) when handling incoming requests.

First of all, let’s see how AsyncLocalStorage compares with cls-hooked , one of the most popular user-land CLS APIs, in this scenario.

Benchmark against cls-hooked

As we see in the above diagram, AsyncLocalStorage is significantly faster than cls-hooked with both plain callbacks and async/await syntax.

Now, let’s try to measure the overhead of the new API when compared with the same web app with a no-op CLS implementation, i.e. an implementation that does nothing (and, thus, does not enable any async hooks).

Benchmark against a no-op CLS

As you may see, when your code is callback-based, the overhead is almost nothing. With async/await syntax it becomes more noticeable, but the penalty is not that high and there is some space for future improvements.

Call to Action

At this point you might be curious about maturity of AsyncLocalStorage and whether it is stable enough to be used in your app or library.

I’d say that the API needs some time to “settle down” (for instance, this PR involves noticeable changes) at this point (March 2020), but early birds are more than welcome.

Once AsyncLocalStorage becomes more stable API-wise, developers who deal with user-land CLS libraries, like cls-hooked , should definitely consider switching to the core module. Also, a backport to Node.js v12 is on its way, so that should help with broader adoption.

As a Node.js community member, I’d like to thank all core collaborators who made AsyncLocalStorage possible (especially, Vladimir de Turckheim who put a lot of energy into the API). On the other hand, as a co-author of this core CLS API, I promise to do my best to spread the word and advocate for AsyncLocalStorage to make Node.js, as a platform and an ecosystem a little bit better.