LoginSignup
18
10

More than 5 years have passed since last update.

Elixirの分散処理(Node)とMnesia

Last updated at Posted at 2018-02-10

 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を立ち上げます。

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

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

端末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を認識しています。

端末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が0です。node_bで実行させたときは、remote nodeなので、node番号の9921になります。

端末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の感じがつかめたのではないでしょうか

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を作っておきます。

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

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

端末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])の引数に2つのnode名を指定します。これで両方のnodeにおいてschemaが同期されます。端末BでもMnesiaをスタートする必要があります。

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

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

端末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で読んでみます。以下のように無事読めることが確認できました。

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

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

端末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にも無事反映されているのが確認できます。

端末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」

18
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
10