Fl4m3Ph03n1x

Fl4m3Ph03n1x

When to use pools VS tasks?

Background

I have been reading Elixir in Action and I have noticed that in an example, the author decides to forgo Tasks and use pools.

The reasoning

The reasoning behind this is that since we were dealing with a Database, and since the DB may be overrun by multiple simultaneous queries, we need to limit its access somehow via a pool.

Thus, the idea I get from the book is that if we have a resource we want to keep from overusing, we should use a pool, otherwise, if our resource is practically unlimited, we should go for Tasks since they are much simpler.

A personal case

As an example of this lets assume I have a service A that needs to notify another service B by sending 2000 requests/second to B (which has no problem in handing it).

In this specific case, would it make sense to use a pool of processes in A for it to use as senders to B?

Opinions?

Since I don’t have any resource here that needs to be safeguarded and processes are so cheap to create I don’t think a pool would make sense.

What do you think ?

Most Liked

keathley

keathley

I think there’s a few really important points to solidify here:

The first is that even if you’re running a Task for each request, those requests are still going to be queued in some fashion on the BEAM while they wait to be scheduled and executed. If you start 1000 tasks and the beam crashes you lost potentially 1000 requests. So I’m not sure stateless vs. stateful is a good comparison to draw here, mostly because its not an accurate comparison. A potentially more appropriate decision is to decide if you’re going to send messages “at most once” or “at least once”. The current semantics you’ve drawn up in the crashing scenario are “at most once”. That might be fine for your use case but its meaningful to make that decision consciously.

A second, and maybe more important point, is that while its potentially useful to imagine that the downstream service has infinite resources and infinite scale out, the harsh reality is that neither of those claims are true. Services have limits, networks have limits, and both of them can experience faults. This is why it’s generally useful to implement batching and pooling. You minimize the overhead required to service the number of requests you have.

Saying that the batching and queueing makes it harder to scale your app just isn’t an accurate statement, especially when you consider the system as a whole. We can think of these 2 systems as a combination of queues. Requests are queued in the first service, sent to the downstream service, the downstream service needs to handle them. It can do some of this work in parallel but given that it’s a stable queue there will always be work in progress, waiting in a queue somewhere. If the downstream service has a slowdown for whatever reason then our total time spent in the system queue (the first service queue and the second service queue combined) increases. This will cause slowdown throughout the whole system and can easily overwhelm your upstream service. There are a bunch of ways of handling this (load shedding, back-pressure, etc.).

One of the ways we’ve had the most success is to use pools for our communication to downstream services and dynamically increase and decrease the number of works available in the pool based on the number of good responses were getting. If we start overwhelming the downstream service we dramatically lower the number of workers and start load-shedding (or return cached good responses) in the upstream service. This allows our downstream services time to heal before we start sending even more traffic their way.

All of these techniques are more about allowing for more overhead in your services and gracefully handling transient failures. While its totally possible (and maybe even reasonable) to spawn a Task per request, depending on your specific needs and guarantees around message delivery you may want to consider a more robust solution.

sasajuric

sasajuric

Author of Elixir In Action

Maybe I should backtract to the original question here.

One reason why I introduced the pool in Elixir in Action was precisely to warn the readers about a potential overload when unlimited concurrency is used. So the message you should take from the book is that it’s definitely good to think about overload scenarios. However, I don’t mean to imply that pooling is the only, or the best option in all such cases.

Which brings me to my comment about batching. I made that comment from the standpoint of load control (which is what IMO pooling is also about). The reason why I introduced this approach to the discussion was to show that there are other ways of controlling the load.

I agree with others here that both approaches are effectively stateful. If BEAM or the underlying machine goes down, whatever you’re doing in-flight will be lost.

It is, however, true that with batching you end up with possibly larger crash effects. If the queueing process crashes, you might lose more in-flight data, compared to a single task. Likewise, if the processing of the batched items fails on the consumer side, you might end up with more failed requests. This is a trade-off of the batching approach, and something you need to account for when considering it.

Either way, I don’t see any general reasons why each technique (or any other load control technique) would prevent scaling (particular reasons might of course exist in concrete scenarios). The statefulness of the queue is local, so you can e.g. still have multiple batching queues spread across multiple machines, just like you can have multiple pools spread across multiple machines.

In any case, all my comments were made from the standpoint of the load control, not scaling. So pooling in the book is used to control the load (it’s also used for teaching purposes, as a fairly simple but realistic concurrent challenge). The batching is mentioned in this thread for the same reason.

sasajuric

sasajuric

Author of Elixir In Action

Do you control both services?

If so, the first thing I’d personally consider is batching, because I sometimes find that sending X requests at once results in much less processing than X time sending one request, and that can really do wonders for performance/stability.

Other than that, pooling + queueing + shedding can be used to control the load, i.e. to make sure that the target service doesn’t get overloaded. More recently I tend to bypass popular pooling libraries (poolboy & friends), and instead use my own parent library which I find has the good balance of flexibility/simplicity for these kinds of challenges.

Where Next?

Popular in Questions Top

chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

Other popular topics Top

New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement