One correction: Futures are not necessarily run on a separate thread pool. If that were always true, they would offer no performance advantage over the thread-per-function or thread-per-request model, and would burden us with more complex code.
The advantage of futures comes when they are used in conjunction with AIO, they provide a decent abstraction for dealing with it. The performance benefits come from using AIO, Futures just provide a convenient way to deal with the results.
If Play was built on Tomcat instead of Netty, and you made various blocking calls inside the functions called by the Futures, you wouldn't get any performance benefits.
I try to add something in the end about thread pools. I didn't want to go into the thread/Core, different thread pools for IO/computation and thread pool for blocking direction to make it more complex.
I imagine that Futures are something like JavaScript Promises. I just thought I should mention this because "Web Developers" was mentioned in the title, but the topic itself is Scala oriented.
Nothing wrong with this, but it could mention Scala in the title.
I've struggled what to name developers who write server side web code and thought web developer is better than frontend developer.
[Edit: And I didn't want to make this about Scala but be insightful to others as well. "For" in Scala is very specific though I admit, but I'd add other examples on how to combine Futures in other languages (perhaps not Haskell as a lot has been written here before, much better than I could)]
Just want to add that as much as I dislike scala syntax, this future based workflow with the Play Framework is ridiculous fast. If you can factor your controllers and request most of the models in parallel things get really fast.
Does exists something similar in another language, with a good web framework? I really like the performance, but the syntax throws me off a bit.
Now...my negative view on using Futures as per the examples in the guide:
Futures (and concurrency in general) make your code much harder to reason about, and much harder to debug. Sequential code running in one thread makes life much easier for the developer.
Faced with the problem presented in the guide (3 method calls total 400 ms), I'd try hard to optimise the methods. Once I've squeezed every potential performance gain out of the sequential method, I'd then investigate caching. Finally, if I still had significant performance problems would I then look at concurrency.
That's a strange criticism of futures to make, because the great strength of them is that you should be able to think about them sequentially, and then as long as they do not have conflicting side effects that model will hold to a concurrent implementation.
Agreed. There's a basic level of thread safety that any server code needs to meet[1]: there aren't global variables or objects shared between requests that are modified in unsafe ways. Once you have that, futures are often pretty easy to reason about.
You do have to make sure you're being careful what you pass into your futures--perhaps using immutable objects or defensive copies. It's not trivial, but it's not horrible either.
[1] Yeah, I know there are cases where people use separate processes each running single-threaded, but c'mon.
I agree with concurrency making code harder to reason and read. Beside flow variables I've found Futures a readable solution though.
The example should be considered IO code, which is hard to speed up as you're limited by the other side, not your code. Also I'd say it's much faster in real production environments but you have more calls than three to backend services. I will try to make the calling of backend services clearer in the guide, thanks.
I appreciate the article. I was confused by one part though. You talk about how to combine multiple Futures into a Future of a tuple and give two example on how to do this. One that will not execute the futures in parallel:
// Returns Future[(User, Seq[Message], Seq[JobOffer])]
// ** NOT EXECUTED IN PARALLEL **
val result = for (
user <- userByEmail("stephan@ubercto.de"); // User
messages <- newMessages; // Seq[Messages]
offers <- newOffers // Seq[JobOffer]
) yield {
// do something here
(user, messages, offers)
}
and another that will:
// Futures are created here
val userF = userByEmail("stephan@ubercto.de");
val messagesF = newMessages
val offersF = newOffers
// Returns Future[(User, Seq[Message], Seq[JobOffer])]
// ** Executed in parallel **
for (
user <- userF;
messages <- messagesF;
offers <- offersF
) yield {
(user, messages, offers)
}
and I'm having a hard time seeing the essential difference between these two bits of code. How is it that creating the futures before the 'for' expression results in the Future of the tuple being executed in parallel? I'm thinking that the 'for' expression gets expanded to basically the same flatMap/map expression in both cases. And that each 'step' in this flatMap/map expression can only be evaluated after the previous 'step' has finished, so neither of these approaches will run in parallel.
14 comments
[ 4.1 ms ] story [ 44.4 ms ] threadThe advantage of futures comes when they are used in conjunction with AIO, they provide a decent abstraction for dealing with it. The performance benefits come from using AIO, Futures just provide a convenient way to deal with the results.
If Play was built on Tomcat instead of Netty, and you made various blocking calls inside the functions called by the Futures, you wouldn't get any performance benefits.
I try to add something in the end about thread pools. I didn't want to go into the thread/Core, different thread pools for IO/computation and thread pool for blocking direction to make it more complex.
I've struggled what to name developers who write server side web code and thought web developer is better than frontend developer.
[Edit: And I didn't want to make this about Scala but be insightful to others as well. "For" in Scala is very specific though I admit, but I'd add other examples on how to combine Futures in other languages (perhaps not Haskell as a lot has been written here before, much better than I could)]
Also, I'd remove the mention of monads, for fear someone would stop to look that up, and enter the shit-storm that is monad explanations.
About monads: Feared that too, will remove monad references and add something to the end about monads.
I feel like the key to any successful monad explanation is going functors->applicatives->monads.
Does exists something similar in another language, with a good web framework? I really like the performance, but the syntax throws me off a bit.
Now...my negative view on using Futures as per the examples in the guide:
Futures (and concurrency in general) make your code much harder to reason about, and much harder to debug. Sequential code running in one thread makes life much easier for the developer.
Faced with the problem presented in the guide (3 method calls total 400 ms), I'd try hard to optimise the methods. Once I've squeezed every potential performance gain out of the sequential method, I'd then investigate caching. Finally, if I still had significant performance problems would I then look at concurrency.
You do have to make sure you're being careful what you pass into your futures--perhaps using immutable objects or defensive copies. It's not trivial, but it's not horrible either.
[1] Yeah, I know there are cases where people use separate processes each running single-threaded, but c'mon.
The example should be considered IO code, which is hard to speed up as you're limited by the other side, not your code. Also I'd say it's much faster in real production environments but you have more calls than three to backend services. I will try to make the calling of backend services clearer in the guide, thanks.