Open xeioex opened 4 months ago
I am very concerned about this move. It contradicts everything that was said in your 2018 talk (https://youtu.be/Jc_L6UffFOs), which I fully support. Performance is key; full ES spec coverage is not; njs is not a nodejs replacement.
QuickJS and Ducktape are in the same ballpark.
And QuickJS is garbage collected.
Hi @radsoc,
Thank you for the feedback and for letting us know that NJS is preferred. Let me address some of your questions.
Performance is key; full ES spec coverage is not; NJS is not a Node.js replacement.
When NJS was introduced, we compared it to existing engines, like Duktape, and found that NJS was faster. QuickJS, introduced in 2019-2020, is actually fast, on a level with NJS, and even 2x times faster in some benchmarks, despite its relatively costly GC. (We are currently addressing the performance gap in NJS.)
A custom interpreter can be tailored to the NGINX runtime. Each request should be isolated from others.
The first statement is still true; NJS is definitely faster for short code snippets because it is quite lightweight for creating/destroying instances. It would be 10x times slower to do this in QuickJS, mostly because QuickJS is not optimized for fast instance creation.
To summarize, NJS is still better for the use cases and principles for which it was designed.
At the same time, the situation is different when more people write increasingly complex scripts. Usually, they do not want to write their own scripts; they tend to reuse existing JS libraries. They also want more advanced scenarios for JS scripting in NGINX, like long-lived Workers with persistent JS instances. Since NJS has no GC, this use case is impossible with NJS.
When people reuse existing libraries, their code is quite large, and GC and performance become more important than fast instance creation.
With QuickJS, we want to address the limitations NJS has and compare it with the alternatives. If there is feedback and NJS is still a better option for some use cases, then we will preserve it.
Thank you for this explanation @xeioex.
But I don't see the point in using NGINX for long-lived Workers. For this scenario, V8 and JSC (node/deno or bun) are 30x faster than QuickJS.
So yes, I'm all in favor of preserving NJS.
@xeioex I definitely have some concerns about this as well.
As @radsoc mentioned, Nginx isn't going to function like a browser with long-lived JS contexts that might have occasion to run very lengthy processes. Unless you're streaming a video or something, the entire request lifecycle for an NJS context is going to be supremely short.
It sounds like what you're saying is that QuickJS is as fast or faster at processing JS code once the context is instantiated but is 10x slower at instantiating contexts. If that is correct, then you're basically saying that NJS is about to become 10x less scalable than it currently is due the the fast, short-lived, and voluminous context needs that it has for real-world traffic. If that understanding is correct, you really can't just write that off like it isn't a HUGE problem. Nginx is used on extremely high-traffic sites all of the time. In fact, we use it on some very high-traffic sites and across hundreds of servers.
As this has been described so far, it really sounds like NJS has decided to abandon its core purpose just to quiet a few complaints on the GitHub issues list. I fully understand that people "want" to use off-the-shelf Node modules. That certainly would have made our job a lot easier when we were building our NJS projects. But a lot of Node modules are built for entirely different environments and have a ton of crap in them that really has no business being in a web server scripting solution. There's just no world in which scalability and stability are worth exchanging for "cool" tech that really just silences people who haven't thought out the consequences of doing whatever they're proposing in a web server environment.
What is NJS doing to speed up context instantiation to ensure that the actual use case (a web server) isn't just going to get oblierated by the shift to QuickJS instead?
@lancedockins
Thank you for the feedback. First of all at this moment we do not plan to retire njs engine.
What is NJS doing to speed up context instantiation to ensure that the actual use case (a web server) isn't just going to get oblierated by the shift to QuickJS instead?
We plan to address this issue by using an single QuickJS instance per location and not per each request as NJS does. This solves the problem of slow instantiation. The GC should prevent instance memory from exploding. It introduces potential memory leaks in certain cases though, but it should work in many cases just fine.
@xeioex That's an interesting approach. But I think that it does raise a few other questions.
Hi @lancedockins,
The items 3 is certainly a possibility. There is a trade-off between:
The first allows to implement various cross-requests logic (caching, stat counters). Also it allows to use large routing tables. On the con side this scheme is more vulnerable to suboptimal JS code which can produce non garbagrable leaky structures. In most cases, if you do not do it on purpose, you will do fine.
The second is fast at destruction/creation and is better protected from poor JS code (not bullet-proof though). On the con side: the code has to read for each request its own data from file or other sources, which can be slow.
Have you run any benchmarks for scale comparison on QuickJS in the configuration that you've described vs NJS?
I did some benchmarks, but in a standalone setup, not as a part of nginx.
I'd certainly hope that the net result of the change would be lower resting and runtime lower memory as well as memory growth over time
As a said at the beginning we will not make QuickJS default engine until it is mature enough in terms of speed and functionality. We also do not plan to retire NJS.
@xeioex Can we just have a separate module for NJS runtime and for QuickJS runtime and maybe call it differently, NJS in current state is really just a scripting language but QuickJS will possibly be a fullblown application framework, how this would be different from lua nginx modules in terms of targeted audience?
Hi @s3rj1k,
The plan is to introduce QuickJS as a drop-in replacement for NJS engine. So most of the existing scripts and configurations should work with the new engine.
To clarify, QuickJS is just an JS engine as NJS. By itself it implements JS ES6 and beyond standards. In effect it will be similar to NJS as a scripting language.
QuickJS will be added as an alternative JS engine. QuickJS is supported and developed by a wider community, it has almost full ES spec coverage.
QuickJS will be added in stages:
NJS engine will stay a default JS engine for a while for backward compatibility (at least until QuickJS version is mature enough).
Feel free to ask questions and share you feedback.