読者です 読者をやめる 読者になる 読者になる

分割された mnesia を使う

erlang mnesia

mnesia_frag はテーブル分割をしてくれる素敵な仕組みです。mnesia には制限があり1テーブル最大 2GiB までというモノがあります。これでは色々使えません。

ということで誰もが思いつくのが Sharding のパーティショニングです。レプリケーションはもともと Mnesia は持っているので、それはそれで。

mnesia_frag を使う場合のポイントはいくつかありますが大きく分けて二つです。

一つ目のポイントとしてはテーブルの作成時です。mnesia:create_table する際に、frag_properties を指定します。ここではいくつのテーブルに分割するか、どのノードで分割する等が指定できます。

ここではノードは特に自分だけにして、テーブルを 128 分割にしています。

mnesia:create_table(store,
                    [{frag_properties,
                      [{node_pool, [node()]},
                       {n_fragments, 128}]},
                     {attributes, record_info(fields, store)}]).

もう一つのポイントは操作を全て mnesia:activity/4 を使用するということです。
acrivity を使用する場合内部に dirty 系の関数を使用してはいけません。基本的には transaction 前提でコードを書いてください。mnesia:dirty_read/2 等は使用しないようにしてください。

さらに、mnesia:activity/4 の最後の引数に mnesia_frag を指定してください。これを指定しないと分割テーブルを見ずに通常のテーブルを見に行ってしまいます。また table_info についても通常は acitivity に含む必要はありませんが、mnesia_frag を指定する必要があるので、こちらを指定してください。

F = fun() ->
      mnesia:read(store, Key, read)
    end,
Result = mnesia:activity(async_dirty, F, [], mnesia_frag).
F = fun() ->
      mnesia:table_info(store, size)
    end,
Result = mnesia:activity(async_dirty, F, [], mnesia_frag),

基本的にはこの二つさえ守れば簡単に mnesia_frag の機能を使う事が出来ます。
大量のデータベースを投入してもメモリが許す限り(オンメモリであれば) 2GiB を超えた値を投入することが出来ます。

遊びで書いたコードを張っておきます。

-module(sample).

-compile(export_all).

-include_lib("eunit/include/eunit.hrl").

-record(store, {key, value}).

main() ->
  crypto:start(),
  mnesia:start(),
  mnesia:create_table(store,
                      [{frag_properties,
                        [{node_pool, [node()]},
                         {n_fragments, 128}]},
                       {attributes, record_info(fields, store)}]),
  F = fun(N) ->
        %%?debugFmt("~B", [N]),
        Key = crypto:rand_bytes(32),
        Value = crypto:rand_bytes(32),
        add_store(Key, Value),
        lookup_store(Key)
      end,
  lists:foreach(F, lists:seq(0, 1)),
  %%lists:foreach(F, lists:duplicate(1000000, dummy)),
  %%?debugFmt("TableInfoSize: ~p", [mnesia:table_info(store, size)]),
  count_store(),
  %%mnesia:info(),
  mnesia:info(),
  crypto:stop(),
  mnesia:stop(),
  ok.

lookup_store(Key) ->
  F = fun() ->
        mnesia:read(store, Key, read)
      end,
  Result = mnesia:activity(async_dirty, F, [], mnesia_frag),
  ?debugFmt("Result: ~p", [Result]),
  ok.

count_store() ->
  F = fun() ->
        mnesia:table_info(store, size)
      end,
  Result = mnesia:activity(async_dirty, F, [], mnesia_frag),
  ?debugFmt("Result: ~p", [Result]),
  ok.

add_store(Key, Value) ->
  F = fun() ->
        mnesia:write(store, #store{key=Key, value=Value}, sticky_write)
      end,
  ok = mnesia:activity(async_dirty, F, [], mnesia_frag),
  ok.

おまけとして @itawasa が書いたレプリケーション対応版が gist に乗っていますので URL を。http://gist.github.com/512938