Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing state in Elixir

Recently I tackled a problem which involved updating a large number of key values.

Naturally, I considered using a Map, with operations like Map.put/3.

However this seemed insufficient, given the immutable nature of data structures in Elixir:

iex> m = Map.put(%{}, :a, 1)
%{a: 1}
iex> Map.put(m, :b, 2)
%{a: 1, b: 2}
iex> m
%{a: 1}

I then solved the problem by holding the state of the Map in a GenServer, and updating it using handle_cast/3 calls.

Generally, is this the right approach, or was this too much here?

like image 741
category Avatar asked Dec 18 '22 02:12

category


2 Answers

I then solved the problem by holding the state of the Map in a GenServer [...] Generally, is this the right approach, or was this too much here?

It heavily depends on your goal. There are many different ways to store the state. Rebinding variables like:

m = Map.put(%{}, :a, 1)
#⇒ %{a: 1}
m = Map.put(m, :b, 2)
#⇒ %{a: 1, b: 2}

Does not store anything. It binds the local variable m to RHO and as soon as the control flow leaves the scope, this variable becomes garbage collected. Whether you need the aforementioned map within a single scope, GenServer (and other state holders) is an overkill.


OTOH, if you need to store the state for a long time and share it between different scopes (e. g. between different processes,) GenServer is the simplest way to accomplish that. In Elixir we have Agent module to decrease the boilerplate for GenServer that is used as a simple in-memory storage, but my advice would be to always use GenServer: sooner or later Agent will become too tight for your purposes.

Also, one might use ets module to keep in-memory key-value storage, shared between processes.

dets is a way to store the state between process restarts.

And, finally, mnesia is an OTP native approach to share the state between both restarts and different nodes (in distributed environment.)

like image 191
Aleksei Matiushkin Avatar answered Jan 05 '23 01:01

Aleksei Matiushkin


Your first approach was right, you just got one thing wrong.

You should rebind the variable when you update the map, like here:

iex> m = Map.put(%{}, :a, 1)
%{a: 1}
iex> m = Map.put(m, :b, 2)
%{a: 1, b: 2}
iex> m
%{a: 1, b: 2}

But you gotta undestand here that it doesn't mutate the variable, it creates a new map and rebinds it to the same variable.

Now, this approach is the most simple one and you'd have to pass this map to every function that uses it. As an alternative, you may consider using the Agent module. All the info what it is and what it is used for can be found in its docs.

like image 38
NoDisplayName Avatar answered Jan 05 '23 00:01

NoDisplayName