Kyoto Tycoon を Solaris event ports に対応してみた
ドラクエXで使われてることなどで有名な Kyoto Tycoon をいじってます。周回遅れもいいところな気はしますが。
Kyoto Tycoon は軽量・高速なKVSで、もともとは Linux 上で開発されてますが、Solaris でも動作します。しかし、Linux では非同期IOに epoll が使われているのに対して、Solaris は select にしか対応しておらずパフォーマンスに劣ります。これはちょっと残念です。Solaris にも event ports という epoll に似た仕組みがあるので対応してみることにしました。
Kyoto Tycoon は2012年以降メンテされてないようなので、今なら Redis とか Couchbase あたりを検討した方がよいような気はしますが、非同期IOの練習ということで。
event ports 対応
とりあえず動くようになったので、パッチを github に上げてみました。 ktremotetest で丸2日くらい負荷をかけ続けて動き続けていたのでそれなりに大丈夫なようです。環境がなくて試せてませんが、これで Solaris でも数千以上のコネクションを捌けるんじゃないかと思います。
event ports では、主に port_create, port_associate, port_getn の3つの関数を使います。port_create でイベントを監視するポートを作成し、port_associate で監視したいオブジェクト(今回はソケットのファイルデスクリプタ)をポートに登録し、port_getn で準備ができたオブジェクトを取得する、というのが大まかな使い方です。manページはありますが、載っている例だとエラー処理(というかバグ対応かも)が不十分なようなので、実例を参考にした方がよいと思います(たぶん libevent がおすすめ)。
Kyoto Tycoon の非同期IO処理は、ktsocket.cc に集約されています。このファイルに epoll(Linux), kqueue(FreeBSD), select(それ以外) 用の処理が書かれているので、それらを参考に event ports の処理を加えました。上記のパッチを当てれば Solaris 10 以上で event ports が有効になります。
Kyoto Tycoon の前身の Tokyo Tyrant は event ports に対応していたので、まずは Tokyo Tyrant の実装を参考にしました。でも、ちょっとエラー処理が変な気がする(port_getn をなぜか2回呼んでる)ので、非同期IOライブラリとして有名な libevent と libev を参考にすることにしました。
event ports はかなり buggy なようで、libev のドキュメントからは作者の怒りを感じます。とはいえ Linux の epoll も buggy で悪名高いようなので、もし非同期IOを実装する機会があったら自前実装するよりも libevent か libev を使う方が楽でしょうね。
インストール手順
Solaris 11 に Kyoto Tycoon をインストールする手順です。Solaris 10 でも gcc4 をインストールすればビルドできます。
gcc4 と lua をインストールする。
pkg install gcc pkg install lua
Kyoto Cabinet をインストールする。
tar zxvf kyotocabinet-1.2.76.tar.gz cd kyotocabinet-1.2.76 ./configure --prerix=/opt/kyotocabinet \ LDFLAGS='-L/opt/kyotocabinet/lib -R/opt/kyotocabinet/lib' make make install
Kyoto Tycoon にパッチを当てる。
tar zxvf kyototycoon-0.9.56.tar.gz cd kyototycoon-0.9.56 patch -i kyototycoon-0.9.56-solaris-eventports-patch
Kyoto Tycoon をインストールする。
./configure --prerix=/opt/kyototycoon --with-kc=/opt/kyotocabinet \ LDFLAGS='-L/opt/kyototycoon/lib -R/opt/kyototycoon/lib \ -L/opt/kyotocabinet/lib -R/opt/kyotocabinet/lib' make make install
ベンチマーク
event ports に対応しようと思ったもともとのきっかけは、ktremotetest で見たときに、Solaris だと Linux に比べて半分以下しかパフォーマンスが出てないように見えたことでした。これは epoll と select の差によるものに違いないと思って event ports に対応してみましたが、結論はそうではなかったようです。
event ports 対応で、コネクション数が多いときは性能が出る可能性はありますが、コネクション数が少ないときのパフォーマンスの差はどうも違うところに原因があるようです。
ベンチマークの環境
VMware ESXi 上に、Solaris 11.2 と CentOS 6.5 をインストールして検証しました。物理マシンのスペックは以下のような感じです。自宅サーバです。
CPU | Intel Xeon E5-1265Lv2 (2.5GHz, 4core, 8threads) |
---|---|
Memory | DDR3 8GB |
HDD | SATA 500GB |
Kyoto Tycoon を以下の4パターンでコンパイルして、付属の ktremotetest で負荷を与えました。
- Solaris select (デフォルトのコンパイルオプション)
- Solaris event ports (今回のパッチを適用)
- Linux select (configure時に --disable-event を指定)
- Linux epoll (デフォルトのコンパイルオプション)
ktserver を以下のようなオプションで起動しました。Disk IO の影響を排除するため、オンメモリDB(:)にしています。
ktserver ":#bnum=2000000#msiz=128m"
ベンチマーク結果
ktremotetest を以下のようなオプションで実行し、10万件(10スレッドx10000件)のレコードの set/get/delete の時間を計測しました。
ktremotetest order -th 10 10000
1秒あたりの set/get/delete の件数をグラフにしてみました。
また、元の値(ktremotetestのtime)はそれぞれ以下の通りです。
環境 | set | get | delete |
---|---|---|---|
Solaris select | 12.695s | 12.487s | 12.553s |
Solaris eventports | 10.416s | 10.207s | 10.040s |
Linux select | 5.347s | 4.858s | 4.684s |
Linux epoll | 4.160s | 4.075s | 3.446s |
10スレッド程度では、select と epoll や event ports の差はあまり出ないようです。ktremotetest の上限の64スレッドまで上げてみても傾向は変わりませんでした。差を出すには数千コネクションとかが必要な気がしますが、自宅サーバでは環境を用意するのが難しいのでまたいずれ。
ボトルネック
どうも Linux と Solaris で倍以上の性能差があるように見えるのですが、原因がよくわかりません。ktremotetest で負荷をかけながら truss で見てみると lwp_park/lwp_unpark が多く出ているので、何らかスレッドが停止しているところがあってボトルネックになっているようです。
$ sudo truss -c -p `pgrep ktserver` ^C syscall seconds calls errors close .000 10 fcntl .000 10 fcntl .000 10 lwp_park 5.128 665532 1254 lwp_unpark 4.621 656047 lwp_unpark_all .000 34 yield 1.972 342387 port_associate 1.815 300027 port_dissociate .000 10 10 port_getn .424 58084 lwp_mutex_timedlock .000 3 accept .000 10 shutdown .000 10 recv 2.034 300017 send 3.251 300007 setsockopt .000 40 -------- ------ ---- sys totals: 19.249 2622238 1264 usr time: 20.709 elapsed: 71.210
対して、こっちは Linux の strace です。futex でスレッドがロックしているようで、これは Solaris の lwp_park と同じような回数なので、ロック機構に差があるのかもしれません。ひとまず今回はこのくらいにしておきます。
$ sudo strace -c -p `pgrep ktserver` Process 1476 attached - interrupt to quit ^CProcess 1476 detached % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 96.85 2.271513 4 601159 128 futex 3.14 0.073703 1 55779 epoll_wait 0.00 0.000066 0 178 sched_yield 0.00 0.000000 0 10 accept 0.00 0.000000 0 40 setsockopt 0.00 0.000000 0 20 epoll_ctl ------ ----------- ----------- --------- --------- ---------------- 100.00 2.345282 657186 128 total