Hacker News new | ask | show | jobs
by pron 4412 days ago
Author here. As I explain in the article, I cannot recommend any framework that encourages asynchronous code. It's simply the wrong approach, no matter what functional tricks are used to make it more palatable. And the blocking Play APIs both feel foreign to Java, and provide no benefit over the standard, and widely implemented, JAX-RS.
2 comments

Blocking in Play should be foreign. On modern hardware architectures, writing code that blocks is the equivalent of throwing up your hands and saying "I can't trust myself to write efficient code so I'll just scale out my hardware and hope for the best". This is how Amazon winds up making so much money off Java developers who get constrained by thread pools and wind up spinning up a million instances of m3.medium machines.

This is, imho, what makes Play so fantastic. That you can have def index = Action { hardComputation() } for blocking code, and change it to def index = Action.async { Future { hardComputation() } } and you have magically changed your application to be asynchronous through the request-as-actor paradigm of Play. Play makes it easy to write async code. Play makes it way easier to control configuration for things like thread pools, dispatchers, and the like vs. Jetty. The performance of the nonblocking async code is incredible, and winds up saving a ton of money and developer time.

Doing a 'hardComputation' async provides no advantage. The cpu is limited to how much computation it can do and if you have enough of those 'hardComputations' running simultaneously whether sync or async you will hit the limit of the cpu.

Async is only beneficial in terms of IO.

Preemptive multitasking makes this much more valuable than you portray it. Play also makes it really easy and you don't have to seriously think about it.
You should read the end of the post, then. The asynchronous, non-blocking approach, is always wrong. Regardless of your hardware architecture.
"The asynchronous, non-blocking approach, is always wrong."

http://en.wikipedia.org/wiki/Fallacies_of_Distributed_Comput...

If you have a method that relies upon a DNS query, and your DNS server starts to respond to requests very slowly, your blocking application will run out of threads very quickly, and you'll be spending time poring over thread dumps trying desperately to see why all of your threads are waiting when the load average on your app server is basically zero. Your monitors will be going off like crazy because it appears that your application is flapping, sometimes able to handle a request and sometimes not. You will be tearing your hair out (and Java devs have all done this, though maybe not because of DNS).

If you have a non-blocking application, you will get an alarm that one of your pages may be acting a little slower than normal (even though the rest of your app is running normally). You'll look into the controller source, see that it's performing a DNS query, and poof, your debugging is done and you're off to restart bind.

I think this is an argument for timeouts, fail over, and load shedding not asychrony.

If you have a sufficiently slow DNS you will chew through all the memory available to your non-blocking application in an amount of time that isn't functionally different from how long it will take you to exhaust your threadpool.

The whole point of the lightweight thread approach with shrinking stacks is that you will use the same amount of memory for both and they will fail at roughly the same point.

You won't run out of (lightweight) threads because you can have millions of them. Blocking code on top of lightweight threads give you exactly the same scaling characteristics of nonblocking code.
I know what you mean by the post and your current assertion that non-blocking is "always" wrong regardless of architecture, but I think there are a few caveats that you really need to apply here.

You are talking about a problem domain where concurrency is what you are scaling and where the things that would block are orders of magnitude slower than processor time.

If you were working in a problem domain where latency is what you are scaling and the blocking calls are on the same order as processor time, non-blocking approaches can be best as the blocking mechanism can still carry overhead even with light weight threads.

Maximizing throughput is another beast entirely as well.

Would you mind substantiating why asynchronous code, successful in plenty of places and pretty much every new environment you're likely to run into, is so dogmatically the wrong approach?