memstored 0.1 = memcached + mpio + Tokyo Cabinet

memstored は memcached のバイナリプロトコルをサポートしたハッシュストレージサーバーです。IO戦略ライブラリmpio の信頼性と性能をテストするために開発しました。

IOに mp::iothreads を使用し、バックエンドには Tokyo Cabinet の抽象データベースAPIを利用しているため、高速でスケーラビリティが高く、かつ柔軟性の高いアーキテクチャになっています。プログラムの大部分はライブラリによって実現されているため、プログラム全体の見通しが良く、行数で見ても非常に小さく収まっています。

svn co http://svn.coderepos.org/share/lang/c/mpio
cd mpio/trunk                    && ./bootstrap && ./configure && make && make install && cd ../../
svn co http://svn.coderepos.org/share/lang/c/memstored
cd memstored/trunk && ./checkout && ./bootstrap && ./configure && make && make install && cd ../../

IOアーキテクチャ

マルチコア時代の高速サーバーの実装で紹介したサンプルプログラムと同様に mp::iothreads を使っています。memstored はスレッド間で共有するリソース(=排他処理が必要)が少ないので、すべてマルチスレッドで記述しています。


新しいクライアントを accept(2) するためのスレッドが走っており、コネクションが確立されるとWorkerスレッド*1にソケットが受け渡されます。

Workerスレッドはクライアントからデータを受け取り、memcached プロトコルのパースとデータベースの操作を行います。そのスレッドでそのまま返信を返します。ここで返信のサイズが非常に大きかったときに書き込みがブロックしてしまうのを防ぐため、一度に書き込みきれなかった場合は Output 用のスレッドに受け渡します。

返信は writev(2) を使って送信しています。アドレスが連続していなくてもカーネル内で効率よく処理してくれるので、ヘッダ + データ という形式のプロトコルを効率よく送信できます。memstored はデータベースからデータを取り出してから返信が完了するまでの間に一度もデータをコピーしません*2

この最適化はデータのサイズが大きいときに効果を発揮しそうです。

ストレージ

ストレージには Tokyo Cabinet の抽象データベースAPIを利用しています。

抽象データベースAPIはオンメモリデータベースAPIとハッシュデータベースAPIとB+木データベースAPIの共通のインターフェイスで、関数 `tcadbopen' でデータベースを開く際のデータベース名で具体的にどの種類のデータベースを扱うかを指定することができます。ハッシュデータベースの名前には接尾辞として ".tch" をつけ、B+木データベースの名前には接尾辞として ".tcb" をつけることで区別されます。チューニングパラメータは、名前の後に "#" で区切って "name=value" の形式で指定します。例えば "casket.tch#bnum=1000000#apow=10" などとします。


Tokyo Cabinet第1版基本仕様書

memstored の引数に渡すデータベース名によって、実際に利用されるデータベースの種類が決定されます。ストレージは性能に非常に大きな影響を与えるので、最適なデータベースを選ぶことが重要です。
"db.tch"と指定すればハッシュデータベース、"*#capsiz=1024m"と指定すれば128MBのオンメモリデータベース、"db.tcb#opts=d"と指定すればDeflateによる圧縮が有効になったB+木データベースになります。

マイクロベンチマーク

memcached のベンチマークツールである memslap を使って、memstored と memcached、Tokyo Tyrant の性能を比較してみました。

memslap --test=get --concurrency=128 [--binary]

あらかじめ1万個のエントリを書き込んでおいて、128本のスレッドでそれぞれ1万回GETを行ったときの経過時間を測定しています。キーのサイズは100バイトで、値のサイズは400バイトです。

server storage protocol elapsed
memstored TC Hash DB (#bnum=20011) binary 16.3 sec
memstored TC on Memory DB binary 15.3 sec
memstored TC B+Tree DB (#bnum=20011) binary 16.8 sec
memcached SLAB binary 15.6 sec
memcached SLAB text 16.8 sec
Tokyo Tyrant TC Hash DB (bnum=20011) text 25.0 esc

とりあえずこのテストでは十分な性能が得られていることが分かります。
値のサイズがもっと大きい場合のテストを行ってみたかったのですが、memcached は1MB以上のキー+値に対応していない & テキストプロトコルは1MB以上の値を許可していない ため、比較ができませんでした。memstored は2^32バイトまでのキー+値に対応しています。

Usage

Usage: memstored [options] -s <database> -l <[addr:]port>

options:
  -h                --help           show this message
  -s                --store          database name. see tcadb(3) for details
  -l <[addr:]port>  --listen         listen address
  -B <number=2048>  --initial-size   allocate this size of buffer first
  -N <number=1024>  --reserve-size   reserve at least this size of buffer
  -W <number=2>     --write-threads  number of threads for asynchronous writing
  -R <number=4>     --read-threads   number of threads for asynchronous reading

*1:プログラム中ではmemstored::Connectionクラスで表現

*2:ユーザー空間では