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*/などになる）。

```erlang:元の記述（部分）
{overrides,[]}.
```

```erlang:修正後
{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 し直す。

```sh:
% 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が作成されていることを確認した。

## テスト

```erlang:
% 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の仕組みが、まだよくわかっていません。あしからず。