LMDBをGoとnginxとtrafficserver上のLuaJITから使ってみた

はじめに

Apache Traffic Serverとnginxで使えるLuaJIT用shared dictを作ってみた · hnakamur’s blogものの、実際には使わないままでした。

LMDB

その後、ライブラリとして使えるキーバリューストアを調べてLMDB (Lightning Memory-Mapped Database)にたどり着きました。 説明を読むとOpenLDAP用に開発されたとのことですが、Symas LMDB Tech Infoの Other Projects を見ると他にも多数のプロジェクトから利用されています。 OpenLDAP, Source Repositoryにリポジトリのリンクがあり、GitHubにも読み取り専用のミラーがあります。

nginx + LuaJIT 用のモジュール

さらに、nginx + LuaJIT 用のモジュール Kong/lua-resty-lmdb もあり、それについての記事が 【日本語訳ブログ】Kong Hybrid 展開と DB レス展開のための新しいストレージ エンジン - KongHQ にありました。まずは、こちらを試してみたのですが、私は trafficserver の LuaJIT からも LMDB を使いたいのでラッパーライブラリを自作することにしました。

nginx と trafficserver の LuaJIT から使えるラッパーライブラリを自作してみた

このラッパーライブラリは、nginx のモジュールや trafficserver のプラグインとしては作っていません。 そうではなく、 C 言語で書いた LMDB の薄いラッパーの共有ライブラリとそれを使った LuaJIT 用の Lua のライブラリで構成されています。 この Lua のライブラリをロードできるように適宜 package.path を設定して使う想定です。

LuaJIT 単体から利用する例が nal_lmdb_stderr_ex.lua にあります。 1行目の

local lmdb = require "nal_lmdb_stderr"

"nal_lmdb_stderr" のところを、nginxで使う場合は "nal_lmdb_ngx"、trafficserverで使う場合は "nal_lmdb_ats" に変える必要があります。

また、 lmdb.env_init の呼び出しは nginx の場合は init_worker_by_lua ではなく init_worker_by_lua で行う必要があります。trafficserver の Lua プラグイン では __init__ 関数内で行います。

ログ出力について

nal_lmdb_{stderr,ngx,ats} の違いは lib/log/ ディレクトリのログ出力の実装だけです。stderrでは標準エラー出力にログ出力し、nginxではnginxのログ出力APIを呼び出してエラーログファイルに出力し、trafficserverではtrafficserverのログ出力APIを呼び出してtrafficserverのログファイルに出力するようになっています。

nginxのログ出力APIはnginxのソースコードをコピーして改変したサブセットを lib/log/ ディレクトリに置いてあります。もし将来 nginx のログ出力API周りの実装が変更された場合は、こちらも追随する必要があります。

MDB_env *型のインスタンスを保持するグローバル変数

src/nal_lmdb.c#L8-L20MDB_env * 型のインスタンスを保持する nal_env_t 型とその型のグローバル変数を定義しています。そしてこのグローバル変数はpthread_onceで一度だけ初期化するようにしています。

これは LMDB の intro.doc#L49-L52 の説明を読んでそのようにしています。

そもそも共有ライブラリ内にグローバル変数を定義して良いのかというのが私はよくわかってなかったのですが、c - can I declare a global variable in a shared library? - Stack Overflowによるとオペレーティングシステムに依存する話らしいです。とりあえず Ubuntu で試してみた感じでは、これで期待通り動いているようです。が、また軽く動作確認しただけなので今後問題が発覚する可能性はあり得ます。

Goでは github.com/bmatsuo/lmdb-go を使いました

最初 https://github.com/armon/gomdb を試してみたのですが、サンプルコードを書いてビルドするとエラーが出たので、 https://github.com/bmatsuo/lmdb-go にしました。 lmdb package - github.com/bmatsuo/lmdb-go/lmdb - Go Packages に分かりやすい説明があり ExampleExample (Worker) をベースに少し改変するだけで、私がやりたいことは実現できて簡単で良かったです。