Thoughts on Node.js
I've made fun of Node and argued against its use in a production environment. I've questioned its architecture and its entire reason for being. I've mocked the brogrammers hocking it as magic scaling sprinkles (as I did to the Erlang proponents before them). My main issue with Node at the start was that it had simply not been tested to withstand the type of abuse a high-scale production site would put on it. Now that I don't work on a high scale, performance critical site I can take a step back and read up on some of the things I've been shunning all of these years.
What Node Lacks
Node lacks history and that's still my main hesitation. As a new runtime with a short history there is a lack of good information on how to tune and troubleshoot Node. There is also a proliferation of solutions to very common problems with no clear guidance as to the best practices. This is true of every new language or runtime (I've personally faced it in Scala as well) but it is still something that hinders adoption.
What Node Brings
Concurrency problems are notoriously difficult to debug. It seems that the main problem is that in a program of any reasonable complexity it becomes impossible to reason about every possible concurrency interaction. Many of us try to. Much of the time we think we have a handle on it. But as time goes on I'm more and more convinced I'm just getting lucky on these. Worst of all, concurrency issue often only fail in production, under heavy load, when we can least tolerate failure. Running in a single thread removes this problem.
Some problems are scaled vertically but it's become clear over the years that these solutions are just buying time and that horizontal scalability is a more robust path. Adding threads to a program is taking the vertical scalability approach where more, low-overhead processes running in parallel (possibly on different machines) is the very definition of horizontal scalability.
At first glance the process management issues this creates seem like the kind of thing dev-ops teams go batshit over. Realistically, if you're doing anything of a reasonable size and complexity those types of problems already have to be solved. Even better: those problems have been solved and your dev-ops team probably already has a solution the know how to use and monitor effectively.
Asynchronous by Default
Most programs spend the lion's share of their time blocking on I/O of some sort. While you can go single threaded alone the amount of time spent waiting on I/O translates into wasted resources. The ability to use these "waiting" cycles (e.g. using your CPU while you wait for your disk to spin) is the key to greater efficiency. None of that is earth shattering but very few programs take advantage of it. For example, that last great web framework (Rails) ran as a single thread that spent tons of time in I/O wait. If we know asynchronous I/O can produce huge efficiency gains why aren't we doing it? Well, probably because our mainstream programming languages use synchronous I/O by default.
If you've ever used Java's NIO package you know that adding asynchronous I/O into a language otherwise built around standard, synchronous I/O is painful. If you've written Java and not used NIO then you have proved my point. The NIO package was added in Java 1.4 and while everyone can agree that blocking I/O is the enemy of efficiency very few programs use NIO – and even fewer use it flawlessly.
Where That Leaves Me
The combination of single-threaded processes using asynchronous I/O is nothing new. My first technology job was with AOL and the "SAPI" library at AOL was a framework for these same restrictions when building C programs. Before that AOL had similar PL/I libraries using X.25. You can discount AOL but at the time (late 90's) the scale was impressive and it was being done with ease in many cases.