How we tamed Node.js event loop lag: a deepdive
trigger.dev
trigger.dev
They accidentally wrote synchronous O(n^2) code that hogged the CPU, blocking the event loop, then fixed it. But that doesn’t sound as adventurous…
Otherwise a solid example of using observability tools to debug a live issue.
It’s about how to systematically detect and debug the problem. In Node that’s not a trivial thing to do. That has value
Depends on your code best practices, I've found it way easier than other platforms I've used (C++, Python). Even without explicit interrupts and such.
* No anonymous functions/lambdas (unless they're extremely trivial, but then you probably don't need that in a function at all)
* Avoid recursion
* Functions do one simple thing and return
* Only one return at the end of the function body
* No uncaught exceptions
* Functions that "await" and functions that "compute" are separate ones
* Avoid 3rd party libraries unless it is absolutely necessary
* Write code for a single thread model, scale horizontally with cluster, things like pm2 or just running several node processes
Never had to write them down, tbh, there's definitely many more. This could apply to every language, though, not just JS. You could borrow some practices from realtime computing, as well.
If you do most of these things, debugging with console.trace is straightforward. You can also use one of the flamegraph tools out there for the profiling part.
They only thing you can't control (but you can examine to some extent) is the GC, but in practice that's never been an issue for me since the GC that comes with V8 is really good.
I'm a pretty sophisticated node dev and I haven't heard of the technique of logging spans whenever the event loop blocks for 100 ms (similar to how you get warnings by default in frameworks/chrome for going over 100 ms on event handlers).
Obviously simple to run a setInterval and compare wall clock, but I would have no idea how to detect the actual issue.
As an aside, it’s a bit weird to create a span to mark that something happened, OTel events are made for that
if (n % 1000 === 0) await require('node:timers/promises').setImmediste()
If you sleep in your async functions, other work can flow. Just unblock the other work.Node calls this partitioning your work. https://nodejs.org/en/learn/asynchronous-work/dont-block-the...
Maybe don't use this specific library but it's pretty easy to rebuild everyday array iterators (forEach, reduce, map) to automatically yield every n iterations. https://www.npmjs.com/package/nice-loops