harmon25

harmon25

Can a GenServer state be too "big" and general application architecture

Hoping the old adage that no question is a dumb question holds true with this one…

When building an application that requires maintaining state, should that state be maintained by a single GenServer process? Or should it be split up across multiple processes?
At what point does the state maintained within a single GenServer become too big? Possible?
Does this just boil down to semantics and code organization?

I guess poor performance would be an indicator one might want to split up the state across more processes. Are their more defined best practices in this regard?

Do mnesia or ETS have a role to play in applications which have a lot of state to maintain, but do not require persistence?

Hope the answers to this question will not only help me better understand and build Elixir applications, but other beginners as well!

Thanks all

Most Liked

sasajuric

sasajuric

Author of Elixir In Action

I wouldn’t say there is one true way. It really varies from case to case, depending on what you want to achieve.

The benefit of keeping the state in a single process is that you have strong consistency. At any point in time, there can be only one process accessing the state. On the flip side, since all the requests are serialized, that process may become a sequential bottleneck if used frequently by many different processes. Another important downside is that you lose the entire state when the process fails. The consequences of a single error might be larger than you want.

Splitting the state/responsibilities over multiple processes give you a reversal of the properties above. There is no strong consistency: you can’t read a frozen snapshot of states from multiple processes, nor can you atomically change states in them. On the upside, you have possible performance improvements, and better failure isolation: if one process crashes, the others are still running, so you get to keep most of your service.

Hence, I’d say that choosing one or the other depends on the problem at hand. The work that needs to be done atomically and consistently should reside in a single process. It’s better off splitting independent tasks into multiple processes, for better error isolation as well as performance and scalability. It’s of course never as simple or as black/white as this, but these are the general guidelines I’d recommend. Occasionally diverging from that path for good reasons is fine.

One point about this:

Does this just boil down to semantics and code organization?

Processes are not the tool for code organization, but rather the organization of runtime. If you need to organize the code, modules are the way to do it. Let’s say for example that the state of your process becomes quite complex, but you still want to keep it in the same process. Then, consider implementing the state in one or more separate, (usually) purely data-oriented modules, and have GenServer functions just use those modules.

JEG2

JEG2

Author of Designing Elixir Systems with OTP

Because I had to look that up, I though I would shared that it means, “There ain’t no such thing as a free lunch.”

NobbZ

NobbZ

We have a saying in germany, that there are no dumb questions, but only dumb answers :wink:

I think this is very similar to a discussion we recently had in a programming exercise (imperative) about a global super “object”¹ to hold and maintain state or having multiple small distinct global variables. There hasn’t been a real conclusion, but after half an hour everyone decided to use multiple small states, to reduce levels of idirection.

I don’t know about best practices here, I do not have that much battle proof experience with BEAM, but depending on how your super state is structured I do fear, that it will massively influence garbage collection (and generation ;))

Haven’t used mnesia so far, but using :ets in my erlang applications all the time. Reads and writes are much faster than a GenServer keeping a Map as state. Also there are optimisations in :ets for concurrent reads and writes (even interleaved), which you can’t do with a corresponding GenServer and a Map.

¹: Super-sized-struct with accessors would be a better wording here, since we were using C.

Last Post!

natewallis

natewallis

Great, thanks Greg.

I think I will stick with my current route for now (in a similar vein to Sasa’s article) and see if that works out for me. I figure that way I still get the concurrency benefits of Elixir as each Player is run inside its own channel process without spawning processes willy nilly (as per my first approach)…

I hope I am visualising that correctly.. Based on my observations of my Phoenix app in :observer, I believe I am… :slight_smile:

Where Next?

Popular in Questions Top

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
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

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
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement