Node.js is still way faster than Deno — No and yes, the right answer is: It depends
Recently I came into the following article: Node.js is still way faster than Deno, and I, as 11 years software developer and benchmark maniac, wanted to try it myself.
Just to clear things up, I am not a fanboy, I don’t even like JavaScript to be honest, but I like to do benchmarking and wanted to try it myself, I love Rust, the language Deno is being written in, so I was excited to see how Deno is envolving and to compare with what Node already offers.
Going straight to the point
The benchmark made in the article cited before was a single-core requests/s, and I wanted to try to compare it with a multi-core one.
Why?
Even though your server is single-threaded, requests still may (and will) happen simultaneously.
Multi-thread benchmarks are good to show how the server behaves with concurrent users, it tries to simulate a considerable number of users constantly interacting with the server.
Given that Node and Deno are event-loop based servers (correct my if I’m wrong), they are able to handle multiple requests (if complex and cpu-heavy calculations are not being made) by taking advantage of non-blocking I/O (because you don’t need to block the entire application while the client or database did not finishes their work and respond the server, just put them aside and come back when its ready).
Test cases
To prove my point (that "it depends"), I've setup some benchmark types, that I also made available in my GitHub if anyone want to explore, or to prove that I'm wrong or that I've done something that affected my benchmarks negatively and produced erroneous results (I'm human, I make mistakes too).
The test cases are:
- Latency: Vegeta attack with 100 and 1000 requests/s during 120s (2 minutes)
- Requests/s: JMeter Thread Group with 300 simultaneous users during 120s (2 minutes)
- Requests/s: JMeter Thread Group with 50 simultaneous users during 120s (2 minutes)
- Requests/s: JMeter Thread Group with 1 single user during 120s (2 minutes)
- Requests/s: JMeter Thread Group with 1 single user and fibonacci sequence calculation during 120s (2 minutes)
- Requests/s: JMeter Thread Group with 300 users and fibonacci sequence calculation during 120s (2 minutes)
Vegeta
Node — Latency 100 requests/s
Node — Latency 1000 requests/s
Deno — Latency 100 requests/s
Deno — Latency 1000 requests/s
Benchmark Conclusion
Lets first format the results in a table:
Latencies are close and measured in microseconds, while Deno shows lower maximum latencies, Node hits Deno with lower mean latencies, but on the other side, Deno beats Node in 99th.
This can be seen in the graphs why Deno has higher mean latencies, it does have more spikes that contributes to producing a higher number, but still able to keep a 99th lower than Node.
However, I would advise you to not look to much at this benchmark, as it does not push both server to its limits and the latency difference is really low (in microsecond unit).
I could try higher req/s number, however I prefer to not focused too much in Vegeta benchmarks, but in JMeter ones, as they measure how much req/s those frameworks can handle.
JMeter
Single User/Single Thread
50 Users
300 Users
Benchmark Conclusion
Deno was only able to beat NodeJS in the 300 users benchmark, even though Deno does have lower maximum latency, it loses at 95th and mean latency in two benchmarks (single user and 50 users).
It makes sense that Deno is able to handle more concurrent requests than Node, but it is only in specific case, considering how mature Node is and how young is Deno, both of them shows really good results.
And it does prove the point of Alex Hultman, Node beats Deno at single-core benchmarks!
Also it is very interesting, that on my machine, 50 users benchmark was better than the single-core one.
Fibonacci Sequence Benchmark
The fibonacci sequence algorithm used is the recursive algorithm, as the main goal is to waste CPU resources doing calculations that is fully blocking and ties the server execution to a single request during around 600ms.
This will prevent the server from just putting the request aside until the blocking operation finishes, a thing that happens with database communications.
The results that came from this benchmark are very interesting, so I decided to put another framework on the table (a multi-thread one this time) and see how it compares to Node and Deno.
I chose Ktor (I’ve considered to use Hyper, however I think that it will not be fair) as the framework to compare with Deno and Node. The main reason is that I know that Ktor will outperform Deno and Node in this specific scenario, however, not by too much, it is only to prove my point that single-threaded frameworks can handle concurrent requests (Node and Deno does it in fact), but only when there is no CPU-heavy operations running, because there is no I/O operation to take advantage from.
Look, don't get me wrong, I'm not saying that framework X is better than framework Y, each one is made with a set of use-cases in mind, and they excel in doing it, you choose what is better for you and your projects. If you don't need a multi-thread framework or does not have a multi-core machine, just go for single-thread Event Loop based frameworks. If you prefer those frameworks in all cases, it’s your choice and your business.
Node vs Deno
Single user:
300 users:
Ktor
Single user:
300 users:
Benchmark Conclusion
Ktor was able to handle more requests/s in multi-thread scenario, however still comparable to Node and Deno in single-thread benchmark, and this is the expected results!
Also Deno and Node was not able to keep more than 2 requests/s, and it is normal and there is nothing wrong with the benchmark itself, it is only how those frameworks works.
And, even that Deno was able to handle more requests/s in multi-thread scenario, it was by a low margin, that I will not even consider as a win. Deno and Node are suitable for heavy CPU tasks, but not when concurrent users come to the picture, however, most of the use-cases I've seen, does not involve heavy CPU tasks, they are more I/O bound than CPU bound: database interaction, such as read and write and sockets interaction, like streaming, serving endpoints and communication between services.
Also, the advantages that Deno and Node provides to you as being single-thread worths the price (but as I always say, it depends on the use-case).
Note: the error rate and latency were high because both Deno and Node could not keep up with the number of concurrent users, they were busy doing a CPU intensive task.
Conclusion
In my point of view, based on those benchmarks, Node outperforms Deno in some use-cases, but in others don't. I think that, taking the hype apart, Deno could be used in place of Node, if you plan to serve endpoints to ≃ 300 concurrent users, but I would not say that Deno is mature enough to be comparable with Node in most common scenarios. Maybe someday it will, but they haven't made it yet.
Both of them are event-loop based and built on top of V8 engine, so performance will not be drastically different (when Deno reaches Node single-core performance, if it does), and Alex Hultman benchmark showed that Node outperforms Deno by twice reqs/s (in single-core scenario), and I was able to reproduce the same results with my benchmarks, so, yes, Node still faster than Deno, but as the number of users grow, Deno starts to get close to Node performance.
My Machine
What least affects those benchmarks purpose (which is compare both frameworks) is the machine they are run. The results could be different (like the number of requests), but for comparison purpose this does not affect the final result, unless either Node or Deno does have architeture specific improvements.
Here is my machine specifications:
- Ryzen 5 3600 6-core 12-thread
- 32GB RAM 3000MHz
- 960GB NVMe MP510 3GB/s
And the OS:
- Linux 5.11.16-zen1–1-zen #1 ZEN SMP PREEMPT Wed, 21 Apr 2021 17:22:09 +0000 x86_64 GNU/Linux
Node and Deno versions:
- Node v14.16.1
- Deno: 1.9.2 (release, x86_64-unknown-linux-gnu)
Ktor version:
- 1.5.4 (Kotlin 1.5.0)
Thanks for reading!