Elixirの分散処理(Node)とMnesia

　ElixirにはETSとMnesiaという2つのDBがデフォルトでついてきます。何故PhoenixではPostgreSQLなのかという疑問が素朴に残りますが、Mnesiaが気になったので少し調べました。とりあえず魅力に感じる点は、2点あります。第1に、特別に何かをインストールすることなしに使える点です。Elixir(Erlang)さえインストールされていれば、すぐに使えるのです。第2にレプリケーションが簡単に作れるということです。

　最初にElixir自身の分散処理の基礎であるNodeをみて、それからMnesiaの基本的な使い方を見たいと思います。



# １．Nodeについて

　ざっくりNodeについて感じをつかみたいと思います。ElixirはErlang VM（Beam）上で動作します。そして動作しているErlang VM自体はnodeと呼ばれます。Elixirの分散処理は複数のNode上に展開されるものです。BeamはホストOS（Linux等）上で動いている小さなOSとみることができます。Beamはイベント処理やプロセススケジューリング、メモリ管理、名前サービス、プロセス間通信などをハンドリングします。nodeは他のnodeと通信できます。同じホスト上のnodeだけでなく、LAN上のnodeやインターネット上のnodeと通信できます。

　普通にiexを立ち上げ、Node.selfでカレントのノード名を表示します。名前を指定しないと :nonode@nohost という名前になります。

```sh
$iex
iex(1)> Node.self
:nonode@nohost
```

　nameを指定してiexを立ち上げ、Node.selfでカレントのノード名を表示します。指定した名前が表示されます。

```sh
$iex --name sand@mypress.local
iex(sand@mypress.local)1> Node.self
:"sand@mypress.local"
```

　snameを指定してiexを立ち上げ、Node.selfでカレントのノード名を表示します。snameはショート名です。指定した名前に@www13134ufがついて表示されます。www13134ufは私のサーバのホスト名です。


```sh
$iex --sname miles
iex(miles@www13134uf)1> Node.self
:miles@www13134uf
```


　さて、２つのnodeを接続してみましょう。端末Aと端末Bの2つ開き、それぞれiexを立ち上げます。

```sh:端末A
$iex --sname node_a
iex(node_a@www13134uf)1>
```

 Node.listで接続されている他のnodeを表示します。最初は空リストですが、 Node.connectでnode_aと接続するとnode_aが認識されるようになります。

```sh:端末B
$iex --sname node_b
iex(node_b@www13134uf)1> Node.list
[]
iex(node_b@www13134uf)2> Node.connect :"node_a@www13134uf"
true
iex(node_b@www13134uf)3> Node.list
[:node_a@www13134uf]
```

　node_aの方でNode.listを行うと、こちらもnode_bを認識しています。

```sh:端末A
iex(node_a@www13134uf)1> Node.list
[:node_b@www13134uf]
```

　node_aでカレントのnode名を表示する関数funcを定義し、node_aとnode_bで実行させます。それぞれnode_aとnode_bのnode名が表示されます。spawnされた結果、プロセス番号も表示されます。node_aで実行させたときは、localのnodeなので第一fieldが０です。node_bで実行させたときは、remote nodeなので、node番号の9921になります。

```sh:端末A
iex(node_a@www13134uf)2> func = fn -> IO.inspect Node.self end
#Function<20.99386804/0 in :erl_eval.expr/5>
iex(node_a@www13134uf)3> Node.spawn(:"node_a@www13134uf", func)
:node_a@www13134uf
#PID<0.97.0>
iex(node_a@www13134uf)4> Node.spawn(:"node_b@www13134uf", func)
#PID<9921.100.0>
:node_b@www13134uf
```


　さて以上でNodeの感じがつかめたのではないでしょうか



# ２．Mnesiaについて

　さてMnesiaですが、一応 real-time distributed database management system と呼ばれています。relational/object hybrid data model でもあります。使いやすいということです。それと保存先をRAM/DISCで選択できて、RAMの時は爆速だそうです。ここでの例は全てRAMですので、nodeを再起動するとデータは消えますのでご注意ください。transactionsもokです。まあ、Erlangの現場で鍛えられてきたのでしょうから、筋金入りなのかもです。

　以下の説明は少々長いですが[「分散DBMS「Mnesia」の並列処理」](https://codezine.jp/article/detail/5014)からの引用です。

```
　Mnesiaは、Erlangという言語をベースとした並列プログラミング用のオープンソース開発環境であるErlang OTP
に付属している高機能のデータベース管理システム（DBMS）です。Mnesiaは真の分散DBMSなので、
世界中の何千ものノード間でデータを分散し、複製し、断片化することも朝飯前です。
ユーザーがしなければならないのは、Mnesiaデータベースの分散先となるさまざまなErlangノード
を実行することだけです。
　Mnesiaという名前になった経緯は、少々眉唾ではありますが、もともとは「Amnesia（健忘症）」という名前
だったものを、エリクソンの重役が「データベースに『物忘れ』を連想させる名前を付けるのはいかがなものか」と
発言したことから、エンジニアが「A」の文字を取って「Mnesia」とし、「すべてを記憶するもの」の意味を持たせたと
言われています。
```


　iexを名前付きで立ち上げます。mnesiaはErlangのコアに含まれているので、Elixirからは:mnesiaで参照できますが、aliasでMnesiaで参照できるようにします。最後に Mnesia.create_schemaでDBを作ります。


```sh
$iex --name sand@mypress.local
iex(sand@mypress.local)2> alias :mnesia, as: Mnesia
:mnesia
iex(sand@mypress.local)3> Mnesia.create_schema([node()])
:ok
```

　Mnesiaにおけるschemaとはファイルシステム上の特殊なディレクトリのことで、node名が使われます。ここでiexを起動したカレントディレクトリを見てみます。Mnesia.sand@mypress.localというnode名でディレクトリができているのが確認できます。

```
$ ls
Mnesia.sand@mypress.local
```

　Mnesiaをスタートします。

```sh
iex(sand@mypress.local)5> Mnesia.start()
:ok
```

　テーブルを作り、一行書いて、それを読みます。

```sh
iex(sand@mypress.local)6> Mnesia.create_table(Person, [attributes: [:id, :name,
:job]])
{:atomic, :ok}

iex(sand@mypress.local)6> Mnesia.create_table(Person, [attributes: [:id, :name,
:job]])
{:atomic, :ok}
iex(sand@mypress.local)7> Mnesia.dirty_write({Person, 1, "山田太郎", "歌手"})
:ok
iex(sand@mypress.local)8> Mnesia.dirty_read({Person, 1})
[{Person, 1, "山田太郎", "歌手"}]
```


　ここまでで、Mnesiaが驚くほど簡単に利用できることがわかったと思います。

　次に２つのnodeでMnesiaを動作させ、同期がとれている様子を見たいと思います。端末Aと端末Bでiexを立ち上げ、Mnesiaのaliasを作っておきます。


```sh:端末A
$ iex --sname node_a
iex(node_a@www13134uf)2>  alias :mnesia, as: Mnesia
:mnesia
```

```sh:端末B
$ iex --sname node_b
iex(node_b@www13134uf)1> alias :mnesia, as: Mnesia
:mnesia
```

　これで2つのnodeが走っている状態です。端末Aでschemaを作り、Mnesiaをスタートします。

```sh:端末A
iex(node_a@www13134uf)3> Mnesia.create_schema([:node_a@www13134uf,:node_b@www131
34uf])
:ok
iex(node_a@www13134uf)4> Mnesia.start()
:ok
```

　ここでcreate_schema([:node_a@www13134uf,:node_b@www13134uf])の引数に２つのnode名を指定します。これで両方のnodeにおいてschemaが同期されます。端末BでもMnesiaをスタートする必要があります。


```sh:端末B
iex(node_b@www13134uf)2> Mnesia.start()
:ok 
```

　これで2つのnode上でMnesiaが同期をとりながら走っているはずです。端末Aでテーブルを作成し、データを書き込み、それが端末Bで読めることを確認します。

```sh:端末A
iex(node_a@www13134uf)5>  Mnesia.create_table(Person, [attributes: [:id, :name,:
job]])
{:atomic, :ok}
iex(node_a@www13134uf)6> Mnesia.dirty_write({Person, 1, "山田太郎", "歌手"})
:ok
iex(node_a@www13134uf)7> Mnesia.dirty_read({Person, 1})
[{Person, 1, "山田太郎", "歌手"}]
```

　端末Bで読んでみます。以下のように無事読めることが確認できました。

```sh:端末B
iex(node_b@www13134uf)3> Mnesia.dirty_read({Person, 1})
[{Person, 1, "山田太郎", "歌手"}]
```

　念のために、今度は端末Bでデータを書き込み、それが端末Aで読めることを確認します。


```sh:端末B
iex(node_b@www13134uf)4>  Mnesia.dirty_write({Person, 2, "佐藤花子", "プログラマ
"})
:ok
iex(node_b@www13134uf)5>  Mnesia.dirty_read({Person, 2})
[{Person, 2, "佐藤花子", "プログラマ"}]
```

　以下のようにnode_aにも無事反映されているのが確認できます。

```sh:端末A
iex(node_a@www13134uf)8> Mnesia.dirty_read({Person, 2})
[{Person, 2, "佐藤花子", "プログラマ"}]
```

　さてここまでで簡単にMnsiaの分散が設定できることが確認できました。実際に使うとなると、まだまだいろいろなことがありそうですが、とりあえずはスタートの敷居が低いのは大変ありがたいことです。ElixirからMnsiaを扱うドキュメントだけですかね、欠点は。

　さらに詳しいMnesiaのレプリケーションについては、以下の記事が大変わかりやすいので、紹介して終わりたいと思います。
[「mnesiaをelixirから使ってみる」](https://qiita.com/k1complete/items/18dba4b8cf0da5952e59)


　またMnesiaをより使いやすくしたライブラリです。これも触ってみたいです。
[「amnesia - mnesia wrapper for Elixir」](https://github.com/meh/amnesia)
[「Store Everything With Elixir and Mnesia」](https://code.tutsplus.com/articles/store-everything-with-elixir-and-mnesia--cms-29821)