[erlang-questions] Architecture: How to achieve concurrency when gen_server:handle_call waits?
Sun Feb 7 15:12:51 CET 2016
I quite like your analogy. I think the core issue is what is it that the gen_server is for.
The scenario you describe is roughly along the lines of "gen_servers seem to be a bottleneck in the way of lots of parallel calls” and that is precisely correct. You would normally only use a gen_server when that bottleneck is a good thing - when there is some sort of state in the gen_server itself that is mutated by the calls to the gen_server. If all you need are worker threads that operate completely independently then the gen_server is an unhelpful bottleneck and your code could just reside in a simple library module (i.e. just a plain old erlang module - not one adopting a gen_xxx behaviour). This library code can just do the work (typically a good choice if the work is quick, or if the callee actively wants to block until the work is done) or it can spawn a process, do the work in that process and send a message back to the caller when it is done. So, to build on your question 3) - here each call to the API would spawn its own process that would live for the duration of the work and then expire. There are common techniques (which I see mentioned in other replies coming in as I type) to throttle the total number of processes spawned if you might have very large numbers of concurrent calls and need to limit them / provide back-pressure. That said, if you are new to erlang I would err on the side of creating processes freely - if a piece of code isn’t “just” some simple maths or state manipulation there’s a good chance a new process isn’t a bad option :) That way you have small, easily testable elements to work with.
If as a side-effect you have lots of protocols (message flows) between the processes that do not in some way reflect your business rules (do this thing and also that thing at the same time, or do lots of thing X all at the same time), then you’ve probably got too many and they are likely to make the code harder to understand again, so think about whether you’d be better off merging once more. For example if you need to add X & Y and also A & B, just do them one after the other - the complexity caused by splitting the work and then merging the answers is very unlikely to be worth it (ignoring the fact that creating process, while cheap computationally is not quite free!).
Of course, the other reason to have code in gen_xxx modules is so that they can easily be supervised and restarted if something goes wrong - probably not the case in the sort of scenario you described.
Getting the balance right between “library code”, “library code that spawns processes” and "gen_xxx processes” is part of the journey to being an effective erlang programmer. You’ve come up with a really good question, so I’d say you are on the journey already :)
> On 7 Feb 2016, at 13:20, Luke <> wrote:
> The way I'm getting my head around the actor model & OTP is to imagine it's like an office building full of people with specialised jobs (gen_servers), and you send emails to people who work on your request right away and reply when they're done (for a handle_call). If I have used call then it's as if I fire off the email and sit there clicking refresh until I get a reply (or like a literal call, I am on hold until they get back to me), but if I use cast then I just get on with my day and maybe check my inbox again later. I can see a lot of benefits with this kind of organisation of your programs and like it a lot.
> My problem is that when a gen_server is working on something they are busy and can't answer more calls/casts. Is the correct way to achieve concurrency in Erlang to have each gen_server spawn a brand new process and then go back to checking their inbox again? In the office building metaphor, essentially each worker also has access to an infinite pool of interns and they are able to forward tasks, immediately delegating all the work away (I used to work at Ericsson, I can see how this model comes naturally to them :P)
> If this is indeed the correct way to achieve concurrency, I still have the following questions:
> 1 - Why isn't this done automatically behind the scenes in gen_server? When would you ever not want to free up your gen_server to handle more requests?
> 2 - Is it best practice to spawn each new process under the gen_server, a supervisor somewhere, or not all?
> 3 - If your gen_server is being flooded with messages, would one viable solution to achieve concurrency be creating say 10 of the same gen_server under a supervisor, and having any processes passing messages to this "job" randomly pick one, effectively using probability to reduce traffic to each by 1/10 - is there a library/methodology for doing this kind of thing already?
> 4 - This seems like it would be a common piece of code, is this bundled into OTP somewhere? Is this situation what I'm supposed to use gen_event for? Or if I'm completely wrong, what is the actual way programs like yaws achieve high concurrency, as reading the source code has not revealed the answer to me.
> erlang-questions mailing list
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the erlang-questions