2017/05/31

[c/c++]lmdbをマルチスレッドで使いたい

lmdbで、同じDB(といえばよいのか?)をマルチスレッドで使おうと思っている。
まあ、DBというくらいだから、ロックなんかは自動的にやってくれるだろうと何も考えていなかったのだが、なんだかうまくいっていない。


テスト用だからとエラーチェックをしていなかったので、どこでエラーを出しているか確認用のサンプルを作った。
lmdb multithread open fail

スレッド関数でトランザクションまで開いた後スリープし、その間に別スレッドで同じことを行うだけだ。
どこでエラーになったかというと、openだった。

fail: mdb_env_open(2)=16(Device or resource busy)

うーん、mdb_env_open()はEBUSYを返さないのかと思ったが、そうでもないようだ。


mdb.cを見ると、mdb_env_map()でEBUSYを返す箇所があるから、mdb_env_open()の引数でMDB_FIXEDMAPを使っているせいかもしれない。
言い訳になるが、lmdbのサンプルソースがMDB_FIXEDMAPしか使っていないから、それがデフォルトだと思ったのだよ。。。



そもそも、LMDBにとってマルチスレッドはどういう扱いなのだろうか?

LMDB: Lightning Memory-Mapped Database Manager (LMDB)

ロックファイルを使うからどうのこうの、と書かれている。
それ以外の基本的な考え方としては、

  • A thread can only use one transaction at a time, plus any child transactions.

ということで、トランザクションの間は1つしかアクセスできないようだ。
これがlmdb内でやってくれるのか、外側でそうしないといかんのか。。。


ああ、今回のエラーについては、その次に書いてあった。

  • Use an MDB_env* in the process which opened it, without fork()ing.
  • Do not have open an LMDB database twice in the same process at the same time.

まず、mdb_env系のAPIはopenしたプロセスで使用しなさい、とのこと。
それ以外のAPIだったらfork先でも使ってよいということかな?

また、openは2回するな、と。
今回はこっちでエラーになったのかな?

試しに、MDB_FIXEDMAPフラグを外して実行してみると・・・エラーが出なくなった。
トランザクションでエラーになるかと思ったが、そうでもなかった。
これはbeginさせているだけだからかも。


どこかにまとめて書かれていないものか探したら、Getting Startedページにあった。
LMDB: Getting Started

  • LMDBはPOSIX lockを使う
    • このlockは、1つのプロセスで複数回ファイルを開くと問題がある
    • だから、mdb_env_open()は1つのプロセスで複数回実行するな
      • その代わり、スレッド間でLMDB environmentを共有しなさい
      • そうせんかったら、複数openした箇所のどこかがcloseするとロックが削除されてしまう
  • transactionは、デフォルトでTLS(thread local storage)に入っている
    • read-onlyなtransactionでよいなら、MDB_NOTLSオプションを使ってもよいよ


そんなわけで、LMDBとしてはmdb_env_open()を複数回呼んではいかんのだけど、今回発生したエラーはそれが原因ではなかったし、複数回呼んだからといってエラーになるというわけでもないことが分かった。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。