Overview

It seems like Nodejs is gaining popularity at a surprising rate, with npm becoming one of the largest package managers in the world. I say it’s surprising because it’s gaining popularity despite there being no conceivable reason to ever use Nodejs in any context. There is literally nothing Nodejs can do that other languages and frameworks can’t do better. In this post I will be exploring the claims made on Nodejs’s website and by the community at large.

Nodejs Is Webscale

From the Nodejs website:

Node is designed to build scalable network applications… Thread-based networking is relatively inefficient and very difficult to use… HTTP is a first class citizen in Node, designed with streaming and low latency in mind. This makes Node well suited for the foundation of a web library or framework.

Let’s address the first claim, that a thread-based networking stack is inefficient while Nodejs is designed to be scalable and low latency. A simple load test was performed that connects to webservers written in various languages / frameworks and issues GET requests from multiple simultaneous connected clients.

Queries per second

Mean latency

The performance test used Nodejs v7.10.0 (server code), rustc 1.19.0-nightly (server code), clang version 3.8.1-23 (server code), and go 1.8.1 (server code) running on a Google Compute Engine virtual machine instances of n1-standard-4 size in the us-west1-a region (client code).

Nodejs performed by far the worst in terms of latency and performance, magnitudes slower than C++ or Go. Of course it’s no suprise that Javascript is slower than C++, but how much slower is shocking to me. I’d also like to point out that the C++ server (listed as Boost.Asio on the graph) is not using an asynchronous event-based networking model, but a thread-per-connection blocking network model, the one described in the previous quote as being “relatively inefficent”. See my previous article on the comparison of sync vs async networking IO which goes into why the cargo cult around async network programming is bullshit.

While it’s true that asynchronous network IO is slightly faster than thread-per-connection blocking IO, the use of Javascript nullifies any performance benefit you would receive from writing async code. The last time I made a post about IO performance a few people pointed out the C10K problem as the reason why async is becoming popular. Nodejs, despite using async, is clearly not the solution to the C10K problem; in fact, a sutable solution already exists for this problem and it’s called a load balancer.

Update: I received a few complaints that it wasn’t fair to use a 4-core machine to perform the load tests, since Nodejs does not support a multi-threaded event loop by default (like that’s somehow my fault?). So I decided to rerun the exact same benchmarks above but with a n1-standard-1 GCE instance which is a single core CPU.

Queries per second

Mean latency

As you can see, Nodejs is still dead last. I also want to point out again that the C++ server, which is showing the highest QPS and lowest latency, is using a thread-per-connection blocking IO network model and is said to be “inefficient” according to the Nodejs website.

Thread-based Networking Is Difficult To Use

Another claim on the Nodejs website is that thread-based networking is very difficult to use (I’m not making this up, check the about page). I find it hard to believe that callback hell is a better way of writing code than a proper sequentially executed method, i.e.:

fs.readdir(directory, (err, files) => {
  if (err) {
    ...
  } else {
    files.forEach((filename, fileIndex) => {
      fs.readFile(directory + filename, (err, data) => {
        if (err) {
          ...
        } else {
          console.log(data);
        }
      });
    });
  }
});

Vs.

for filename in fs::read_dir(directory).unwrap() {
  let mut file = File::open(filename.unwrap().path()).unwrap();
  let mut contents = String::new();
  file.read_to_string(&mut contents).unwrap();
  println!("{}", contents);
}

Clearly the thread-based synchronous IO example is much cleaner and readable. There have been improvements to ES7 that adds the async and await keyword which uses promises / futures, but that’s only a hack on top of a hack. I would much rather deal with mutexes than this callback garbage we’ve been subjected to.

Finally, I can conclude this section with a paradox: If you cared about performance you wouldn’t use Nodejs; if you don’t care about performance why are you torturing yourself writing complicated asynchronous code?

Nodejs Is Built On Javascript

I’ll keep this section short, since I feel like this has been talked about to death already. Basing a framework on a language written in a few weeks isn’t a great idea.

As many of you know, all numbers in Javascript are doubles, which leads to hilarious bug reports such as: Node occasionally gives multiple files/folders the same inode (I’d also like to point out the lovely callback hell given in the example).

Node sometimes reports different files/folders to have identical [inode] values… it could also be caused by node.js converting libuv’s 64 bits st_ino field to a double, which won’t be lossless for large numbers… [Javascript] doesn’t have a 64 bits integral type, only a 64 bits floating-point type. Values >= 2^53 and <= -2^53 cannot be represented exactly and get rounded up or down.

Let that sink in, due to the limitations of Javascript, a supposed “server-side framework” is unable to accurately represent an inode on a server.

Conclusion

Despite claims made on the Nodejs webiste, Node is neither easy to use, scalable, or low latency. There’s literally no reason to ever use Nodejs for anything. Just in case you’re wondering what I think of other languages:

Great C++, Rust
Pretty Good Java, C#, Scala, Go
Meh Ruby, Python
Garbage Javascript, PHP