ElixirにはETSとMnesiaという2つのDBがデフォルトでついてきます。何故PhoenixではPostgreSQLなのかという疑問が素朴に残りますが、Mnesiaが気になったので少し調べました。とりあえず魅力に感じる点は、2点あります。第1に、特別に何かをインストールすることなしに使える点です。Elixir(Erlang)さえインストールされていれば、すぐに使えるのです。第2にレプリケーションが簡単に作れるということです。
最初にElixir自身の分散処理の基礎であるNodeをみて、それからMnesiaの基本的な使い方を見たいと思います。
1.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 という名前になります。
$iex
iex(1)> Node.self
:nonode@nohost
nameを指定してiexを立ち上げ、Node.selfでカレントのノード名を表示します。指定した名前が表示されます。
$iex --name sand@mypress.local
iex(sand@mypress.local)1> Node.self
:"sand@mypress.local"
snameを指定してiexを立ち上げ、Node.selfでカレントのノード名を表示します。snameはショート名です。指定した名前に@www13134ufがついて表示されます。www13134ufは私のサーバのホスト名です。
$iex --sname miles
iex(miles@www13134uf)1> Node.self
:miles@www13134uf
さて、2つのnodeを接続してみましょう。端末Aと端末Bの2つ開き、それぞれiexを立ち上げます。
$iex --sname node_a
iex(node_a@www13134uf)1>
Node.listで接続されている他のnodeを表示します。最初は空リストですが、 Node.connectでnode_aと接続するとnode_aが認識されるようになります。
$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を認識しています。
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が0です。node_bで実行させたときは、remote nodeなので、node番号の9921になります。
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の感じがつかめたのではないでしょうか
2.Mnesiaについて
さてMnesiaですが、一応 real-time distributed database management system と呼ばれています。relational/object hybrid data model でもあります。使いやすいということです。それと保存先をRAM/DISCで選択できて、RAMの時は爆速だそうです。ここでの例は全てRAMですので、nodeを再起動するとデータは消えますのでご注意ください。transactionsもokです。まあ、Erlangの現場で鍛えられてきたのでしょうから、筋金入りなのかもです。
以下の説明は少々長いですが「分散DBMS「Mnesia」の並列処理」からの引用です。
Mnesiaは、Erlangという言語をベースとした並列プログラミング用のオープンソース開発環境であるErlang OTP
に付属している高機能のデータベース管理システム(DBMS)です。Mnesiaは真の分散DBMSなので、
世界中の何千ものノード間でデータを分散し、複製し、断片化することも朝飯前です。
ユーザーがしなければならないのは、Mnesiaデータベースの分散先となるさまざまなErlangノード
を実行することだけです。
Mnesiaという名前になった経緯は、少々眉唾ではありますが、もともとは「Amnesia(健忘症)」という名前
だったものを、エリクソンの重役が「データベースに『物忘れ』を連想させる名前を付けるのはいかがなものか」と
発言したことから、エンジニアが「A」の文字を取って「Mnesia」とし、「すべてを記憶するもの」の意味を持たせたと
言われています。
iexを名前付きで立ち上げます。mnesiaはErlangのコアに含まれているので、Elixirからは:mnesiaで参照できますが、aliasでMnesiaで参照できるようにします。最後に Mnesia.create_schemaでDBを作ります。
$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をスタートします。
iex(sand@mypress.local)5> Mnesia.start()
:ok
テーブルを作り、一行書いて、それを読みます。
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が驚くほど簡単に利用できることがわかったと思います。
次に2つのnodeでMnesiaを動作させ、同期がとれている様子を見たいと思います。端末Aと端末Bでiexを立ち上げ、Mnesiaのaliasを作っておきます。
$ iex --sname node_a
iex(node_a@www13134uf)2> alias :mnesia, as: Mnesia
:mnesia
$ iex --sname node_b
iex(node_b@www13134uf)1> alias :mnesia, as: Mnesia
:mnesia
これで2つのnodeが走っている状態です。端末Aでschemaを作り、Mnesiaをスタートします。
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])の引数に2つのnode名を指定します。これで両方のnodeにおいてschemaが同期されます。端末BでもMnesiaをスタートする必要があります。
iex(node_b@www13134uf)2> Mnesia.start()
:ok
これで2つのnode上でMnesiaが同期をとりながら走っているはずです。端末Aでテーブルを作成し、データを書き込み、それが端末Bで読めることを確認します。
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で読んでみます。以下のように無事読めることが確認できました。
iex(node_b@www13134uf)3> Mnesia.dirty_read({Person, 1})
[{Person, 1, "山田太郎", "歌手"}]
念のために、今度は端末Bでデータを書き込み、それが端末Aで読めることを確認します。
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にも無事反映されているのが確認できます。
iex(node_a@www13134uf)8> Mnesia.dirty_read({Person, 2})
[{Person, 2, "佐藤花子", "プログラマ"}]
さてここまでで簡単にMnsiaの分散が設定できることが確認できました。実際に使うとなると、まだまだいろいろなことがありそうですが、とりあえずはスタートの敷居が低いのは大変ありがたいことです。ElixirからMnsiaを扱うドキュメントだけですかね、欠点は。
さらに詳しいMnesiaのレプリケーションについては、以下の記事が大変わかりやすいので、紹介して終わりたいと思います。
「mnesiaをelixirから使ってみる」
またMnesiaをより使いやすくしたライブラリです。これも触ってみたいです。
「amnesia - mnesia wrapper for Elixir」
「Store Everything With Elixir and Mnesia」