Using synchronous I/O is only simpler if you never have to synchronize threads, which you always do.
After taking a closer look at Discourse, it seemed to me that there was something amiss about how the Ruby community handles concurrency -- non-blocking servers (thin), with blocking database I/O (ActiveRecord), behind a round robin load balancer (nginx), combined with external processes for asynchronous tasks (sidekiq). That is a lot of tooling to handle concurrency. Comparatively I think Node has some advantages.
I am a proponent of Node, not because I think it is perfect, but I do think it does many things right. If you are building a significant web app you can't avoid JavaScript. And it is nice to be able to use and move code between the client and the server.
Also, this might sound weird, but I think there are some benefits to forcing developers to think differently about blocking vs non-blocking operations, since there are magnitudes of performance differences between the two. I think the outcome will be better architected and performing applications, but that's just a hunch.
Maybe Erlang and Go do concurrency right with message passing and light weight threading models, but they haven't taken the web development world by storm either. I think Go has a good chance, but time will tell.
Using synchronous I/O is only simpler if you never have to synchronize threads
No, but you have to synchronize callback chains, which is a lot worse in a large system. Otherwise it is kind of a tautology, well you don't have threads so you can't synchronize threads. But one main reason to synchronize threads is to prevent shared data from being corrupted from concurrent access. But now also the business logic is split based on IO points.
Also, this might sound weird, but I think there are some benefits to forcing developers to think differently about blocking vs non-blocking operations,
No doubt it is important to be aware how these things work probably down to libc level. What makes node.js tick, v8 and libuv, what do those do, and so on.
since there are magnitudes of performance differences between the two.
Completely disagree. There are no general magnitudes of performance differences for all applications between those two. There are application specific and architecture specific.
I think the outcome will be better architected and performing applications, but that's just a hunch.
Spreading business logic across callback boundaries or sprinkling yields or thens, is not always a good way to handle things. It is in some cases but as a general approach, I think it is pretty bad.
Maybe Erlang and Go do concurrency right with message passing and light weight threading models, but they haven't taken the web development world by storm either
One reason is because people don't understand the underlying technology and its limitations or are just not aware about other programming paradigms (especially when handling distributed and concurrent issues).
So I tend to not go along much with "well many are doing things this way so it must be better". Over the years I believe more that just following the "many" crowds get one an average result. Your stuff is just as broken or works just as well as anyone else.
That is why it is important to look at Go, Erlang, Rust, Haskell, Prolog etc. There provide new ways of thinking that could help you accelerate faster than the rest of the crowd.
That is a lot of tooling to handle concurrency. Comparatively I think Node has some advantages.
Node forces everything into a single-threaded event loop. That's great if you're writing a chat server. It's not so great if you want to do something that actually uses the CPU.
Ruby has threads (which execute in parallel on multiple CPU cores with JRuby/Rubinius), async I/O, and ways to build hybrid systems out of them cleanly, like Celluloid
But there is a lot of tooling required to solve problems seen by common web apps.
Node is doing it differently, and it has advantages and disadvantages. One disadvantage is if you are doing CPU bound work, you will have to consider how you design your app more carefully. But that's the case with any language or platform.
Event driven servers have been around since the dawn of the internet and the select() call. Node has simply made it easier to write them in a higher level language. I personally think the event model makes a lot of sense for writing network servers, which web servers are. Also having a high performance HTTP implementation built into a platform for building applications for the web is a significant benefit.
I think two prevalent models are going to emerge for writing concurrent servers in the future. Event driven platforms like Node and actor/messaging passing systems like Go and Erlang. The languages and platforms that do not do those things well, I believe, will become less popular.
Ruby developers who care about writing high performance, concurrent web applications aren't going to use Thin unless they're also going to be using an evented framework and libraries. So no Rails, no ActiveRecord. When Thin is used with Rails, it's usually running only one request per process. And I don't think that's very common: Unicorn and Passenger are much more common for request-per-process Rails deployments. Thin is available for evented setups, and Puma is available for threaded setups. Choosing mismatched or less than optimal stacks is hardly a Ruby-specific issue (or one Node avoids in any way other than removing the option entirely), and it tends to indicate either ignorance or a lack of need for the best performance possible.
nginx or Apache are used because they're specifically designed for serving public HTTP traffic, and they have a lot of other features that tend to come in handy. They're also extremely well-optimized for delivering static content. There's no compelling reason to reinvent the wheel there -- HTTP application containers mounted behind dedicated HTTP servers is a serviceable, easily understood model with a lot of distinct benefits.
Finally -- background processing is frequently CPU-intensive. Evented concurrency is generally not useful there. Ruby has great options for both threaded[1] and multiprocess background processes, and the ideal model might not even be the same as what you choose for the frontend.
Comments
Using synchronous I/O is only simpler if you never have to synchronize threads, which you always do.
After taking a closer look at Discourse, it seemed to me that there was something amiss about how the Ruby community handles concurrency -- non-blocking servers (thin), with blocking database I/O (ActiveRecord), behind a round robin load balancer (nginx), combined with external processes for asynchronous tasks (sidekiq). That is a lot of tooling to handle concurrency. Comparatively I think Node has some advantages.
I am a proponent of Node, not because I think it is perfect, but I do think it does many things right. If you are building a significant web app you can't avoid JavaScript. And it is nice to be able to use and move code between the client and the server.
Also, this might sound weird, but I think there are some benefits to forcing developers to think differently about blocking vs non-blocking operations, since there are magnitudes of performance differences between the two. I think the outcome will be better architected and performing applications, but that's just a hunch.
Maybe Erlang and Go do concurrency right with message passing and light weight threading models, but they haven't taken the web development world by storm either. I think Go has a good chance, but time will tell.
No, but you have to synchronize callback chains, which is a lot worse in a large system. Otherwise it is kind of a tautology, well you don't have threads so you can't synchronize threads. But one main reason to synchronize threads is to prevent shared data from being corrupted from concurrent access. But now also the business logic is split based on IO points.
No doubt it is important to be aware how these things work probably down to libc level. What makes node.js tick, v8 and libuv, what do those do, and so on.
Completely disagree. There are no general magnitudes of performance differences for all applications between those two. There are application specific and architecture specific.
Spreading business logic across callback boundaries or sprinkling yields or thens, is not always a good way to handle things. It is in some cases but as a general approach, I think it is pretty bad.
One reason is because people don't understand the underlying technology and its limitations or are just not aware about other programming paradigms (especially when handling distributed and concurrent issues).
So I tend to not go along much with "well many are doing things this way so it must be better". Over the years I believe more that just following the "many" crowds get one an average result. Your stuff is just as broken or works just as well as anyone else.
That is why it is important to look at Go, Erlang, Rust, Haskell, Prolog etc. There provide new ways of thinking that could help you accelerate faster than the rest of the crowd.
Node forces everything into a single-threaded event loop. That's great if you're writing a chat server. It's not so great if you want to do something that actually uses the CPU.
Ruby has threads (which execute in parallel on multiple CPU cores with JRuby/Rubinius), async I/O, and ways to build hybrid systems out of them cleanly, like Celluloid
Yes it is true, if your app is CPU bound, the event driven model has limitations. But many applications are not CPU bound.
There's a reason why that extra tooling exists: to solve a wider range of problems
But there is a lot of tooling required to solve problems seen by common web apps.
Node is doing it differently, and it has advantages and disadvantages. One disadvantage is if you are doing CPU bound work, you will have to consider how you design your app more carefully. But that's the case with any language or platform.
Event driven servers have been around since the dawn of the internet and the select() call. Node has simply made it easier to write them in a higher level language. I personally think the event model makes a lot of sense for writing network servers, which web servers are. Also having a high performance HTTP implementation built into a platform for building applications for the web is a significant benefit.
I think two prevalent models are going to emerge for writing concurrent servers in the future. Event driven platforms like Node and actor/messaging passing systems like Go and Erlang. The languages and platforms that do not do those things well, I believe, will become less popular.
Event driven I/O like this? (hey look ma, it's callback-free!)
https://github.com/celluloid/nio4r
https://github.com/celluloid/celluloid-io
Actor framekworks like this?
http://celluloid.io
High performance web servers like this?
https://gist.github.com/YorickPeterse/9555037
A few things:
Ruby developers who care about writing high performance, concurrent web applications aren't going to use Thin unless they're also going to be using an evented framework and libraries. So no Rails, no ActiveRecord. When Thin is used with Rails, it's usually running only one request per process. And I don't think that's very common: Unicorn and Passenger are much more common for request-per-process Rails deployments. Thin is available for evented setups, and Puma is available for threaded setups. Choosing mismatched or less than optimal stacks is hardly a Ruby-specific issue (or one Node avoids in any way other than removing the option entirely), and it tends to indicate either ignorance or a lack of need for the best performance possible.
nginx or Apache are used because they're specifically designed for serving public HTTP traffic, and they have a lot of other features that tend to come in handy. They're also extremely well-optimized for delivering static content. There's no compelling reason to reinvent the wheel there -- HTTP application containers mounted behind dedicated HTTP servers is a serviceable, easily understood model with a lot of distinct benefits.
Finally -- background processing is frequently CPU-intensive. Evented concurrency is generally not useful there. Ruby has great options for both threaded[1] and multiprocess background processes, and the ideal model might not even be the same as what you choose for the frontend.
[1]: I'll take the opportunity to mention my Ruby background processing system, Woodhouse: https://github.com/mboeh/woodhouse