Continuation Local Storage for easy context passing in Node.js
Recently I’ve been working on a Node.js project where we needed to keep track of which requests were generating calls to some other pieces of code. Passing a request identifier around was not viable since it would require to change too many APIs.
We solved the problem using cls-hooked, a small package that uses the async_hooks
experimental Node.js API to implement Continuation Local Storage.
What is Continuation Local Storage?
Some languages have Thread Local Storage. This is a way to attach global data to the thread itself. Due to the asynchronous and single-threaded nature of Node.js this is not useful. Instead, we use Continuation Local Storage (CLS).
CLS is a mechanism that allows the attachment of data to the current asynchronous execution context. It uses async_hooks to keep track of asynchronous context changes and to load and unload the data associated with it.
Building an Express middleware
First we need to create a namespace. Namespaces are a way to scope global data so we don’t end up getting values from somebody else. We create a namespace with the createNamespace
function.
Our express middleware is going to be quite simple. We’re going to generate an uuid
and store it in the namespace.
As you can see, the set
method is called within a run
. The requestId
value is going to be available from any code called inside that run
block, and that’s why we are calling next()
inside the run
as well. Calling next()
inside the run
will make the requestId
available for any other middleware and controller in our express application.
We can now build our complete express application and use the requestId
anywhere:
A word on performance
Even though the solution presented above is clean and requires little coding, it has a major drawback: the usage of the async_hooks
API.
The async_hooks
API is still experimental so it could possibly change in the future, but what is worse is that enabling it has a big impact on performance. This is currently being discussed in this github issue.
Benedikt Meurer wrote this great benchmark comparing performances with and without async_hooks
enabled.
If you are writing a critical piece of code you probably want to stay away from async_hooks
. You’d also want to stay away from async_hooks
if you’re writing a library, since you could impact performance for your users just for your convenience. If the problem you’re dealing with does not match neither of these scenarios, CLS may be a great way of making things simpler.
Enjoy!