RaftのErlang実装ことはじめ

# はじめに
<!-- 発端や概要を記載 -->
こんにちは、RetailAIXの[@long10lang](https://twitter.com/long10lang)です。さて、advent calendar何書こう？というわけで、あれこれ考えた結果、[Raft](https://raft.github.io/)について書こうと思い立ちまして、それなら単にRaftの説明をしたところでいまさらしょうがないので、[ra](https://github.com/rabbitmq/ra)というErlang実装をご紹介しようと思います。


# 目次
<!-- タイトルとアンカー名を編集 -->
1. [Raftについて](#Raftについて)
1. [raの導入](#raの導入)
1. [raの使い方](#raの使い方)
1. [まとめ](#まとめ)
1. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#Raftについて"></a>
# Raftについて


とはいえ、まずRaftについても少しご紹介しておきましょう。Raftとは、コンセンサスアルゴリズムのひとつで、コンセンサスアルゴリズムというのは、平たくいえば「複数の処理が走っていたとしてもデータの整合性をちゃんと合わせるための方法」のことです。こちらの[アニメーションによる説明](http://thesecretlivesofdata.com/raft/)がとてもわかりやすいです。

詳しい説明は抜きにして、なぜいくつもあるRaft実装のうち、Erlang実装を紹介するのかというと、やはりOTPが落ちにくいという点に魅力があります。Raftがいくらデータの整合性を保ったとしても実装自体が頻繁に落ちてしまっては、結局データ欠損を免れません。そこで、どのくらい実用性があるかどうかはさておき、可能性を探るべく調査をしてみましたので、その辺りをご紹介できればというのが、こちらの記事の発端です。

<a id="#raの導入"></a>
# raの導入

ではまず、しのごのと言わずraを利用できるようにしていきましょう。環境はWSL2で行なっていきます。

```bash
$ git clone https://github.com/rabbitmq/ra.git
```

環境構築に必要なやつらをインストールします。この辺りはすでに入っている場合は不要です。

```bash
$ sudo apt install libssl-dev automake autoconf libncurses5-dev gcc
```

次に、kerlを入れてやります。kerlはマジ便利ツールですので、ぜひ入れてください。

```bash
$ curl -O https://raw.githubusercontent.com/kerl/kerl/master/kerl
#実行権限もつけましょう
$ chmod a+x kerl
```

これで、kerlが利用できるようになったか確認します。バージョンのリストが表示されたらOKです。

```bash
$ ./kerl list releases
```

それでは、Erlang/OTPをインストールしていきましょう。今日(2022.12.1)時点での最新バージョンは、25.1.2ですので、そちらをインストールしたいと思います。まずはビルドします。大体、終わるまでに２、３分かかります。

```bash
$ ./kerl build 25.1.2
...
#終わるとこういった文言が表示されます。
Erlang/OTP 25.1.2 (25.1.2) has been successfully built
```

Erlangがちゃんとビルドされたかどうか確認します。

```bash
$ ./kerl list builds
25.1.2,25.1.2
```

はい、ちゃんとビルドされていますね。では、こちらをインストールしたいと思います。

```bash
$ ./kerl install 25.1.2 ~/kerl/25.1.2
Installing Erlang/OTP 25.1.2 (25.1.2) in /home/user/kerl/25.1.2...
You can activate this installation running the following command:
. /home/user/kerl/25.1.2/activate.fish
Later on, you can leave the installation typing:
kerl_deactivate
```

ぼくの場合は、shellにfishを使っているので、こんな感じの指示が表示されます。その通りに実施した後、ちゃんとインストールされたか確認してみましょう。

```bash
./kerl list installations
25.1.2 /home/user/kerl/25.1.2
```

出ました。これでようやくErlangのインストールが終了です。ちょっと遊んでみましょう。erlとコマンドを打つとインタラクティブシェルが起動します。

```bash
$ erl
Erlang/OTP 25 [erts-13.1.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Eshell V13.1.2  (abort with ^G)
1>
```

ここで、ちょこっと演算などやってみます。「.」をお忘れなく。

```bash
1> 5+7.
12
2> Num = 4+12.
16
3> Num + 8.
24
4> q().
ok
5>
```

ここで、せっかくなのでElixirも導入したいと思います。raを使う上でErlangよりもElixirの方が色々と使い勝手が良いからです。kerlと同様、Elixirのバージョン管理ツールKiexも入れましょう。

```bash
curl -sSL https://raw.githubusercontent.com/taylor/kiex/master/install | bash -s
```

こちらも、ちゃんとインストールされていれば、以下のコマンドでバージョン一覧が表示されるはずです。

```bash
$ kiex list known
...
    1.13.4
    1.14.0
    1.14.0-rc.0
    1.14.0-rc.1
    1.14.1
    1.14.2
```

Elixirの方は、今日（2022.12.1）の最新版が1.14.2なので、こちらをインストールしましょう。

```bash
kiex install 1.14.2
...
rm -f man/elixir.1
rm -f man/elixir.1.bak
rm -f man/iex.1
rm -f man/iex.1.bak
make[2]: Leaving directory '/home/long/.kiex/builds/elixir-git'
make[1]: Leaving directory '/home/long/.kiex/builds/elixir-git'
Installed Elixir version 1.14.2
Load with:
           kiex use 1.14.2
or load the elixir environment file with:
   source $HOME/.kiex/elixirs/.elixir-1.14.2.env.fish
```

はい、インストールされました。これまた指示通り、使えるように設定しましょう。そして、以下のコマンドを打って問題なく表示されたらElixirのインストールもOKです。

```bash
$ elixir --version
Erlang/OTP 25 [erts-13.1.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Elixir 1.14.2 (compiled with Erlang/OTP 25)
```

Elixirもちょこっと演算など。

```bash
$ iex

Erlang/OTP 25 [erts-13.1.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.14.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 1+1
2
iex(2)> 3-1
2
iex(3)>
```

ではここで、rebar3をインストールします。

```bash
$ git clone https://github.com/erlang/rebar3.git
$ cd rebar3
$ ./bootstrap
$ ./rebar3 local install
```

ここでもまた、シェルにパスを追加して使えるようにしましょう。そして、rebar3が使えるようになったことを以下のコマンドで確認します。

```bash
rebar3 local upgrade
```

はい、これでようやくra導入の準備が整いました。それでは、本題のraを使ったことはじめと行きたいと思います。


<a id="#raの使い方"></a>
# raの使い方

ではまず、raをgit cloneしてきます。

```bash
git clone https://github.com/rabbitmq/ra.git
cd ra
```

次に、各clusterを起動していきます。tmuxなどを使って別ペインで確認すると便利です。

```bash
# cluster1を起動する
$ rebar3 shell --name ra1@127.0.0.1

# cluster2を起動する
$ rebar3 shell --name ra2@127.0.0.1

# cluster3を起動する
$ rebar3 shell --name ra3@127.0.0.1
```

clusterを起動した後に、raアプリケーションをそれぞれ起動します。

```bash
# cluster1でraアプリケーションを起動する
ra:start().
% => ok

# cluster2でraアプリケーションを起動する
ra:start().
% => ok

# cluster3でraアプリケーションを起動する
ra:start().
% => ok
```

起動が全て確認できたら、cluster2のペインを開き、まず手始めにcluster2のメンバを追加してみます。

```bash
ClusterName = dyn_members,
Machine = {simple, fun erlang:'+'/2, 0},

{ok, _, _} =  ra:start_cluster(default, ClusterName, Machine, [{dyn_members, 'ra2@127.0.0.1'}]).
% => ok
```

次に、cluster1に向けてメンバを追加します。同じくcluster2のペインからメンバを追加してください。

```bash
{ok, _, _} = ra:add_member({dyn_members, 'ra2@127.0.0.1'}, {dyn_members, 'ra1@127.0.0.1'}),
ok = ra:start_server(default, ClusterName, {dyn_members, 'ra1@127.0.0.1'}, Machine, [{dyn_members, 'ra2@127.0.0.1'}]).
% => ok
```

そして最後に、cluster3に向けてメンバを追加します。

```bash
{ok, _, _} = ra:add_member({dyn_members, 'ra2@127.0.0.1'}, {dyn_members, 'ra3@127.0.0.1'}),
ok = ra:start_server(default, ClusterName, {dyn_members, 'ra3@127.0.0.1'}, Machine, [{dyn_members, 'ra2@127.0.0.1'}]).
% => ok
```

はい、これで追加したメンバたちを各clusterから確認すると、ちゃんと表示されることを確認してみましょう。

```bash
# cluster1,cluster2,cluster3それぞれから追加したメンバが確認できます。
(ra1@127.0.0.1)5> ra:members({dyn_members, node()}).
{ok,[{dyn_members,'ra1@127.0.0.1'},
     {dyn_members,'ra2@127.0.0.1'},
     {dyn_members,'ra3@127.0.0.1'}],
    {dyn_members,'ra2@127.0.0.1'}}
```

はい、できてますね。

ついでに、せっかくなんでElixirでもclusterを立ち上げてErlangのクラスタとメンバを共有してみようと思います。Elixirでは、まずraを導入するにあたって、以下のMix.exsファイルを用意する必要があります。このファイルを作ったらraのディレクトリの中にとりあえず置いておきましょう。

```elixir
defmodule KV do
  use Mix.Project

  def project do
    [
      app: :kv,
      version: "0.1.0",
      elixir: "~> 1.14",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      {:ra, git: "https://github.com/rabbitmq/ra.git", tag: "v2.4.0"}
    ]
  end
end
```

それでは、以下のiexコマンドでcluster4を起動してみます。普通に起動してもraを読み込めないので、先ほどの定義を`-S mix`オプションで読み込む必要があります。

```bash
$ iex --name ra4@127.0.0.1 -S mix
```

そして、cluster起動後にraアプリケーションを起動します。

```elixir
# start cluster
:ra.start()
```

再び、cluster2のペインを開きElixirで立ち上げたra4clusterのメンバを追加します。

```bash
{ok, _, _} = ra:add_member({dyn_members, 'ra2@127.0.0.1'}, {dyn_members, 'ra4@127.0.0.1'}),
ok = ra:start_server(default, ClusterName, {dyn_members, 'ra4@127.0.0.1'}, Machine, [{dyn_members, 'ra2@127.0.0.1'}]).
% => ok
```

その後、cluster4ペインに戻りメンバを確認します。

```bash
# Check the members from any node
iex(ra4@127.0.0.1)3> :ra.members({:dyn_members, node()})
{:ok,
 [
   dyn_members: :"ra1@127.0.0.1",
   dyn_members: :"ra2@127.0.0.1",
   dyn_members: :"ra3@127.0.0.1",
   dyn_members: :"ra4@127.0.0.1"
 ], {:dyn_members, :"ra3@127.0.0.1"}}
```

おお、ちゃんと追加されてますね！

<a id="#まとめ"></a>
# まとめ

いかがでしたでしょうか？raは直感的で使いやすいですが、APIはあまりまだ多くない印象でした。Elixirで記述できるところが嬉しいですね。スナップショットの復元など実装が捗りそうです。
ただ、RabbitMQチームは、RabbitMQ3.8以降で、[Quorum Queues](https://www.rabbitmq.com/quorum-queues.html)というRaft実装を採用していて、raとの棲み分けってどうなんだろう？（あるいは、raはQuorum Queuesのラッパー？）というのが気になりました。

さて明日は、サーバサイドエンジニアの@kanto-mizoさんが、[Ruby と Go それぞれで依存性の注入を書く](https://qiita.com/kanto-mizo/items/d1691869459c41350381) というタイトルで発表します！ぜひ、お楽しみください〜

<a id="#reference"></a>
# 参考文献
- [Raft](https://raft.github.io/)
- [Raft paper](https://raft.github.io/raft.pdf)
- [ra](https://github.com/rabbitmq/ra)