ram (ram v0.1.0) View Source
Exposes all of the Key Value store APIs.
Ram doesn't have to run on every node of the Erlang cluster. You may start Ram only on those nodes where you need it. When started, Ram creates a logical overlay network running on top of the Erlang distribution cluster.
Nodes where Ram runs form a subcluster: they will synchronize data between themselves, and themselves only. All of the data is replicated on every node of the subcluster.
Quickstart
Elixir
iex(1)> :ram.get("key")
:undefined
iex(2)> :ram.put("key", "value")
:ok
iex(3)> :ram.get("key")
"value"
Erlang
1> ram:get("key").
undefined
2> ram:put("key", "value").
ok
3> ram:get("key").
"value"
Internals
Ram operations are Atomic (take effect on all nodes involved, or on none of the nodes), Consistent (the data is the same across all nodes) and Isolated (operations on different nodes in a network do not interfere with each other). They are not Durable since Ram is an in-memory only database.
To do so, every operation creates a global lock with global:trans/4
in the caller process, which then runs a transaction with a 2-phase commit protocol. If transactions fail, they raise error({commit_timeout, {bad_nodes, BadNodes}})
where BadNodes
is the list of subcluster nodes where the transaction could not complete.
Conflict Resolution
In case of net splits or bad network conditions, a specific Key might get put simultaneously on two different nodes. When this happens, the cluster experiences a Key conflict.
Ram will resolve this conflict by choosing a single Value. By default, Ram keeps track of the time when a registration takes place witherlang:system_time/0
, compares values between conflicting processes and keeps the one with the higher value (the Value that was put more recently). This is a very simple mechanism that can be imprecise, as system clocks are not perfectly aligned in a cluster.
Link to this section Summary
Functions
Looks up a Key.
Equivalent to get(Key, undefined).
Returns the Key's Value or Default if the Key is not found.
Starts Ram manually.
Atomically updates a Key with the given function.
Link to this section Functions
Specs
delete(Key :: term()) -> ok.
Specs
fetch(Key :: term()) -> {ok, Value :: term()} | error.
Looks up a Key.
Returnserror
if the Key is not found.
Specs
get(Key :: term()) -> Value :: term().
Equivalent to get(Key, undefined).
Specs
get(Key :: term(), Default :: term()) -> Value :: term().
Returns the Key's Value or Default if the Key is not found.
Examples
Elixir
iex(1)> :ram.get("key")
:undefined
iex(2)> :ram.get("key", "default")
"default"
iex(3)> :ram.put("key", "value")
:ok
iex(4)> :ram.get("key")
"value"
Erlang
1> ram:get("key").
undefined
2> ram:get("key", "default").
"default"
3> ram:put("key", "value").
ok
4> ram:get("key").
"value"
Specs
put(Key :: term(), Value :: term()) -> ok.
Specs
start() -> ok.
Starts Ram manually.
In most cases Ram will be started as one of your application's dependencies, however you may use this helper method to start it manually.Specs
stop() -> ok | {error, Reason :: term()}.
Specs
subcluster_nodes() -> [node()] | not_running.
Specs
update(Key :: term(), Default :: term(), function()) -> ok.
Atomically updates a Key with the given function.
If Key is found then the existing Value is passed to the fun and its result is used as the updated Value of Key. If Key is not found, Default is put as the Value of Key. The Default value will not be passed through the update function.
Examples
Elixir
iex(1)> update_fun = fn existing_value -> existing_value * 2 end
#Function<44.65746770/1 in :erl_eval.expr/5>
iex(2)> :ram.update("key", 10, update_fun)
ok
iex(3)> :ram.get("key")
10
iex(4)> :ram.update("key", 10, update_fun)
ok
iex(5)> :ram.get("key")
20
Erlang
1> UpdateFun = fun(ExistingValue) -> ExistingValue * 2 end.
#Fun<erl_eval.44.65746770>
2> ram:update("key", 10, UpdateFun).
ok
3> ram:get("key").
10
4> ram:update("key", 10, UpdateFun).
ok
5> ram:get("key").
20