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 で負荷を与えました。

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スレッドまで上げてみても傾向は変わりませんでした。差を出すには数千コネクションとかが必要な気がしますが、自宅サーバでは環境を用意するのが難しいのでまたいずれ。

ボトルネック

どうも LinuxSolaris で倍以上の性能差があるように見えるのですが、原因がよくわかりません。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