Edited at

Elixirプログラムにmnesia_eleveldbモジュールを組み込もうとしてうまく動作しなかった問題


はじめに

Erlangのライブラリには、ディスクに保存する型のKey-Valueストアとして detsが組み込まれている。ただし、detsには、ファイルサイズの2GB制限やordered_setをサポートしていないという制約がある。

Key-Valueストアのエンジンとして、Googleがオープンソースとして公開しているLevelDBは、よい代替手段である。eleveldbモジュールは、LevelDBをErlangに組み込んだものであり、mnesia_eleveldbモジュールは、Mnesiaの組み込みDBとしてeleveldbを利用するためのモジュールである。しかし、通常の手順でmnesia_eleveldbモジュールを組み込もうとして、うまくビルドできなかったため、解決までの手順のメモとしてこの記事を残す。


問題

Elixirのプロジェクトで、Mixの依存モジュールとして、mnesia_eleveldbを登録すると、eleveldbも依存モジュールとして組み込まれる。そのままビルドして実行すると、:mnesia.create_table/2 で leveldb_copiesオプションを指定して LevelDBのテーブルを作成しようとすると、

10:26:03.028 [warn]  The on_load function for module eleveldb returned:

{:error, {:load_failed, 'Failed to load NIF library: \'Cannot open "/xxx/src/elixir/test_mnesia_eleveldb/_build/dev/lib/eleveldb/priv/eleveldb.so"\''}}

というエラーが発生する。メッセージに記述されているとおり、eleveldb.soは作成されていない。mnesia_eleveldbモジュールを組み込まず、eleveldbモジュール単体を組み込んでビルドした場合は、eleveldb.soは作成された。


解決

Elixirのフォーラムで、eleveldbは、rebar3用に設定されていないからoverridesを使え( https://github.com/erlang/rebar3/issues/801 )、という記述があったので、記事の通りに修正してみる。

_build/dev/lib/eleveldb/mix.rebar.config を編集して、最終行のoverridesを追加する(パスの斜体部分は、/dev/や/rel/などになる)。


元の記述(部分)

{overrides,[]}.



修正後

{overrides,[

{override, eleveldb,
[
{artifacts, ["priv/eleveldb.so"]},
{pre_hooks, [{compile, "c_src/build_deps.sh get-deps"},
{compile, "c_src/build_deps.sh"}]},
{post_hooks, [{clean, "c_src/build_deps.sh clean"}]},
{plugins, [pc]},
{provider_hooks, [{post,
[{compile, {pc, compile}},
{clean, {pc, clean}}
]
}]
}
]
}
]}.

その後、make し直す。

% cd deps/eleveldb

% make
./rebar get-deps
==> eleveldb (get-deps)
./rebar compile
==> eleveldb (compile)
gmake[1]: ディレクトリ '/xxx/src/elixir/test_mnesia_eleveldb/deps/eleveldb/c_src/leveldb' に入ります
gmake[1]: 'all' に対して行うべき事はありません.
gmake[1]: ディレクトリ '/xxx/src/elixir/test_mnesia_eleveldb/deps/eleveldb/c_src/leveldb' から出ます
gmake[1]: ディレクトリ '/xxx/src/elixir/test_mnesia_eleveldb/deps/eleveldb/c_src/leveldb' に入ります
gmake[1]: 'tools' に対して行うべき事はありません.
gmake[1]: ディレクトリ '/xxx/src/elixir/test_mnesia_eleveldb/deps/eleveldb/c_src/leveldb' から出ます
Compiling c_src/eleveldb.cc
Compiling c_src/refobjects.cc
Compiling c_src/router.cc
Compiling c_src/workitems.cc
% ls priv/
eleveldb_multi.schema eleveldb.so* perf_dump* sst_scan*
eleveldb.schema leveldb_repair* sst_rewrite*
%

eleveldb.soが作成されていることを確認した。


テスト

% iex -S mix

Erlang/OTP 21 [erts-10.3.5.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :mnesia.create_schema([node()])
:ok
iex(2)> :mnesia.start
:ok
iex(3)> :mnesia_eleveldb.register
{:ok, :leveldb_copies}
iex(4)> :mnesia.info
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
schema : with 1 records occupying 428 words of mem
===> System info in version "4.15.6", debug level = none <===
opt_disc. Directory "/xxx/src/elixir/test_mnesia_eleveldb/Mnesia.nonode@nohost" is used.
use fallback at restart = false
running db nodes = [nonode@nohost]
stopped db nodes = []
master node tables = []
backend types = leveldb_copies - mnesia_eleveldb
remote = []
ram_copies = []
disc_copies = [schema]
disc_only_copies = []
[{nonode@nohost,disc_copies}] = [schema]
3 transactions committed, 0 aborted, 0 restarted, 2 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
:ok
iex(5)> :mnesia.create_table(:Constant, [leveldb_copies: [node()]])
{:atomic, :ok}
iex(6)> :mnesia.info
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
Constant : with 0 records occupying 292 words of mem
schema : with 2 records occupying 552 words of mem
===> System info in version "4.15.6", debug level = none <===
opt_disc. Directory "/xxx/src/elixir/test_mnesia_eleveldb/Mnesia.nonode@nohost" is used.
use fallback at restart = false
running db nodes = [nonode@nohost]
stopped db nodes = []
master node tables = []
backend types = leveldb_copies - mnesia_eleveldb
remote = []
ram_copies = []
disc_copies = [schema]
disc_only_copies = []
leveldb_copies = ['Constant']
[{nonode@nohost,disc_copies}] = [schema]
[{nonode@nohost,leveldb_copies}] = ['Constant']
4 transactions committed, 0 aborted, 0 restarted, 4 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
:ok
iex(7)> Enum.each(1..100, fn e -> :mnesia.dirty_write({:Constant, e, String.duplicate("abc", e)}) end)
:ok
iex(8)> :mnesia.dirty_read({:Constant, 3})
[{:Constant, 3, "abcabcabc"}]
iex(9)>


蛇足

じゃあ何で eleveldbモジュール単体だとビルドが通るのか? Mixやrebarの仕組みが、まだよくわかっていません。あしからず。