skirino/rafted_value
A Raft implementation in Elixir
{ "createdAt": "2016-06-13T04:08:03Z", "defaultBranch": "master", "description": "A Raft implementation in Elixir", "fullName": "skirino/rafted_value", "homepage": null, "language": "Elixir", "name": "rafted_value", "pushedAt": "2025-05-12T13:07:26Z", "stargazersCount": 57, "topics": [], "updatedAt": "2025-06-22T08:21:33Z", "url": "https://github.com/skirino/rafted_value"}RaftedValue : A Raft implementation to replicate a value across cluster of Erlang VMs
Section titled “RaftedValue : A Raft implementation to replicate a value across cluster of Erlang VMs”Design
Section titled “Design”- Provides linearizable semantics for operations on a replicated value.
- Implements memory-based state machine with optional persistence (writing logs, making snapshots with log compaction, restoring state from snapshot & log files)
- Flexible data model: replicate arbitrary data structure
- Supports membership changes:
- adding/removing a member
- replacing leader
- Each consensus group member is implemented as a
:gen_statemprocess.
Notes on backward compatibility
Section titled “Notes on backward compatibility”- Users of
<= 0.10.3should upgrade to0.10.4before upgrading to0.11.x, due to slight change in log entry format. - Users of
<= 0.7.2should upgrade to0.7.3before upgrading to0.8.x, due to change in interface of:communication_module. - In
0.4.0and0.5.0we migrate from:gen_fsm(deprecated in Erlang/OTP 20) to:gen_statem. This introduces a change in message format of member-to-member and client-to-leader communications. Users of0.3.xmust first upgrade to0.4.0, which understands both old and new message formats, and itself sends messages in the old format (compatible with0.3.x). Then upgrade to0.5.0, which also understands both the old and new message formats and sends messages in the new format. You cannot go directly from0.3.xto0.5.0, since0.3.xdoes not understand messages from0.5.0. - On-disk log format was slightly changed in
0.3.0from that of0.2.x. - Users of
<= 0.1.8should upgrade to0.1.10before upgrading to>= 0.2.0.- RPC protocol of
<= 0.1.8and that of>= 0.2.0are slightly incompatible - Version
0.1.10should be able to interact with both<= 0.1.8and>= 0.2.0
- RPC protocol of
Example
Section titled “Example”Suppose there are 3 connected erlang nodes running:
$ iex --sname 1 -S mixiex(1@skirino-Manjaro)1>
$ iex --sname 2 -S mixiex(2@skirino-Manjaro)1>
$ iex --sname 3 -S mixiex(3@skirino-Manjaro)1> Node.connect(:"1@skirino-Manjaro")iex(3@skirino-Manjaro)1> Node.connect(:"2@skirino-Manjaro")Load the following module in all nodes.
defmodule QueueWithLength do @behaviour RaftedValue.Data def new(), do: {:queue.new(), 0} def command({q, l}, {:enqueue, v}) do {l, {:queue.in(v, q), l + 1}} end def command({q1, l}, :dequeue) do {{:value, v}, q2} = :queue.out(q1) {v, {q2, l - 1}} end def query({_, l}, :length), do: lendThen make a 3-member consensus group by spawning one process per node as follows:
# :"1@skirino-Manjaro"iex(1@skirino-Manjaro)2> config = RaftedValue.make_config(QueueWithLength)iex(1@skirino-Manjaro)3> {:ok, _} = RaftedValue.start_link({:create_new_consensus_group, config}, [name: :foo])
# :"2@skirino-Manjaro"iex(2@skirino-Manjaro)3> {:ok, _} = RaftedValue.start_link({:join_existing_consensus_group, [{:foo, :"1@skirino-Manjaro"}]}, [name: :bar])
# :"3@skirino-Manjaro"iex(3@skirino-Manjaro)3> {:ok, _} = RaftedValue.start_link({:join_existing_consensus_group, [{:foo, :"1@skirino-Manjaro"}]}, [name: :baz])Now you can run commands on the replicated value:
# :"1@skirino-Manjaro"iex(1@skirino-Manjaro)6> RaftedValue.command(:foo, {:enqueue, "a"}){:ok, 0}iex(1@skirino-Manjaro)7> RaftedValue.command(:foo, {:enqueue, "b"}){:ok, 1}iex(1@skirino-Manjaro)8> RaftedValue.command(:foo, {:enqueue, "c"}){:ok, 2}iex(1@skirino-Manjaro)9> RaftedValue.command(:foo, {:enqueue, "d"}){:ok, 3}iex(1@skirino-Manjaro)10> RaftedValue.command(:foo, {:enqueue, "e"}){:ok, 4}
# :"2@skirino-Manjaro"iex(2@skirino-Manjaro)4> RaftedValue.command({:foo, :"1@skirino-Manjaro"}, :dequeue){:ok, "a"}iex(2@skirino-Manjaro)5> RaftedValue.command({:foo, :"1@skirino-Manjaro"}, :dequeue){:ok, "b"}iex(2@skirino-Manjaro)6> RaftedValue.command({:foo, :"1@skirino-Manjaro"}, {:enqueue, "f"}){:ok, 3}iex(2@skirino-Manjaro)7> RaftedValue.query({:foo, :"1@skirino-Manjaro"}, :length){:ok, 4}The 3-member consensus group keeps on working if 1 member dies:
# :"3@skirino-Manjaro"iex(3@skirino-Manjaro)4> :gen_statem.stop(:baz)
# :"1@skirino-Manjaro"iex(1@skirino-Manjaro)11> RaftedValue.command(:foo, :dequeue){:ok, 3}- Raft official website
- The original paper and the thesis about the Raft protocol
- raft_fleet : Elixir library to run multiple
RaftedValueconsensus groups in a cluster of ErlangVMs - skirino’s slides to introduce rafted_value and raft_fleet