たごもりすメモ

コードとかその他の話とか。

fluentd のベンチマークとってみたよ!

入出力プラグインをrubyで書けるのがじつにいい感じの fluentd がいい感じに見える。

fluent/fluentd · GitHub

ので使えるかどうか、使えるとしたらどれくらいのノードを用意すればいいのかについて考えるため、とりあえずベンチマークをとってみた。

結論

以下非常に長くなるので結論だけ書くと、大変使える感じ。現状だとほとんど何も考えずにデータ中継させても秒間1万メッセージ、100Mbpsくらいまでは処理できる。効率よくなるよう流す側も考えてやれば 300Mbps を超えるデータの転送に成功した。だいぶいい感じ。
なおこれは in_scribe および out_scribe を使用した場合で、開発者 @frsyuki によるとMessagePackでのデータ転送の場合はこの倍くらい出るらしい。

もちろんこれは右から左に流しただけなので現実にタグによるルーティングだとかログ中の時間による出力先変更だとかあれやこれやをやるともっと下がっていくのは確かだが、しかしそのへんの問題にはmaxでこれだけ出るのがわかっていればいくらでも対処のしようがあるので、充分実用に耐える数値だと思う。

以下詳細について。

ベンチマーク環境

ベンチマークは負荷をかける側およびかけられる側(fluentd)ともに以下のサーバを使った。

CPU
Intel Xeon L3426 (4Core HT 1.87GHz)
メモリ
16GB
OS
CentOS 5
ruby
1.9.2-p180

なおどちらもGbEで同じスイッチに接続されている。

fluentd は最初 0.10.6 を使っていたが、最初のベンチマーク結果をtweetしてひと晩たったらマルチプロセス化の機能が実装された 0.10.7 が出てて1.5〜4倍高速化しているとかいう話だったので、そっちでもう一度とってみた。

ベンチマークに使用したスクリプト類は以下のリポジトリに全部ある。ただメインの scribe_load.sh やfluentdの設定ファイル conf/fluentd.conf は書き換えながら使ったので適宜過去の状態を参照するのがよろしい。
tagomoris/fluentd-tester · GitHub

構成の詳細は以下の通り。

  • benchサーバ
    • scribe_load.sh 実行
      • 実体はrubyのコードで、事前に生成したscribe用のオブジェクトをfluentd宛に指定レートで投げ続ける
    • scribed 動作(fluentdに行って戻ってきたデータをHDDに書くだけ)
  • fluentdサーバ
    • fluentd 実行
      • in_scribe で来たものをすべて out_scribe でbenchサーバで動作しているscribed宛に投げ返す

両サーバの状況は CloudForecast で記録してて、リソース使用状況やスループットについてはそのデータを参照している。

性能計測の指標について

scribeプロトコルでログメッセージの転送を行う以上、そのパフォーマンスには以下のような要素が絡んでくる。またscribe(Thrift)以外のプロトコルの場合も大枠ではこのあたり考えることは同じだと思う。すべて秒あたりの数ということにする。

RPCレート(call/sec)
外部からfluentd(のin_scribe)に対して行われる、ログメッセージ転送のためのRPC呼び出しの秒あたりの数。MessagePackの場合も同様。HTTPによるログメッセージ転送の場合はHTTPリクエスト数などの指標にあたる。一度のRPC実行にはそれなりのオーバーヘッドがかかるので少ない方がよいが、無理して下げてもよくない場合がありめんどくさい。
RPCあたりのメッセージ数(msg/call)
一度のRPCで転送できるメッセージ数が複数の場合、その数。基本的にはこっちを多くしてRPCレートを下げる方が有効な場合が多い(RPC呼び出しのコストは普通その程度には高い)が、RPCの引数となるデータオブジェクトが巨大になるとそのパースに余計な負荷がかかったりするケースもあり、どこあたりで手を打つかはプロトコルごとに異なる。
メッセージサイズ(bytes/msg)
転送対象となるログメッセージの平均サイズ。自分が転送対象とするデータサイズにあわせてベンチマークをとるべき。ただしここで充分に大きいサイズ*1が許容される場合は複数メッセージをひとつのデータ構造にパックした上で転送にかけるという戦略がとれる可能性がある。*2
メッセージ転送スループット(msg/sec)
秒間で処理可能なメッセージ数。基本的にはここが勝負。
データ転送スループット(bytes/sec, Mbps)
秒間で処理可能なデータ量。最終的にはこれが勝負になる。メッセージ転送スループットが要求に足りない場合、複数メッセージをまとめて1メッセージとして転送することで論理的なメッセージ転送数を稼ぐことができるが、データ転送スループットだけは騙すのが難しい*3。逆に言うと、これが必要なだけ出ているなら何かしらやりようがあると見ていい。

言葉で書くとこんな感じだが、以下のように簡単な式で表せる。

DataThroughput [bytes/sec] = MessageSize [bytes/msg] * MessageThroughput [msg/sec]
MessageThroughput [msg/sec] = RPCRate [call/sec] * MessagesPerRPC [msg/call]

また性能については以下のような一般則がある。(もちろん例外もいろいろある)

  • メッセージ転送スループットは基本的にCPUパワーおよびソフトウェアの構造によって決まる
    • ボトルネックになる場所はソフトウェアの構造によって異なり、マルチプロセス・マルチスレッドによって向上する場合としない場合がある
    • 逆に言うと、マルチプロセス・マルチスレッド化しても向上しない場合はそこがそのソフトウェアの構造上の限界である可能性がある
  • データ転送スループットはメッセージ転送スループットとは独立に定まる場合が多い
    • 大きいデータであれば当然送信に時間がかかるが、現代的なコンピュータであればそれはネットワークデバイスやOSのネットワークドライバによってバックグラウンド(オフロード)で処理される
    • このため、ソフトウェアが既にCPUネックになっている状態でも、1回の処理あたりに送るデータ量(メッセージサイズ)を上げてやることで全体的なデータ転送スループットは向上する可能性が高い

ということで基本的な方針としては以下のようになる。

  1. メッセージ転送スループットについてどの程度が限界かをまず計測する
    • このときメッセージサイズは比較的小さめ(ただし現実的な数値から乖離しない程度)にしておく
    • またRPCレートとRPCあたりのメッセージ数をどの程度のバランスにするのがよいかも変えながら計測し、都合のいいあたりを探る
  2. データ転送スループットの最大値を計測する
    • これが本命、最大でどの程度のデータ転送スループットが出るかを確認する
    • メッセージ転送スループットの限界まで余裕がある状態でデータ量を増やすパターンと、メッセージ転送スループットの限界に近い状態でデータ量を増やすパターンがあるとよい
      • これによりメッセージサイズの大きさに fluentd のメッセージ転送スループットが影響を受けやすいかどうかがなんとなくわかる

ベンチマークをかける側の並列度が上がったときにどうなるかとかも気になる場合はチェックしておくといい。だいたいそんな感じ。

結果

ここから、表ではすべて以下のように書く。

目標メッセージ転送スループット
目標msg/sec
メッセージサイズ
bytes/msg
RPCあたりメッセージ数
msg/call
目標RPCレート
call/sec
メッセージ転送スループット計測値
実績msg/sec
データ転送スループット計測値
実績Mbps

またリソースグラフを左右に並べているところではすべて左側が benchサーバ で右側が fluentdサーバ。

予備計測: scribeにおけるRPCレートとRPCあたりメッセージ数

fluentd 0.10.6 を対象に最初にベンチマークをとったとき*4はメッセージ転送スループット、RPCあたりメッセージ数と目標RPCレートのバランス、データ転送スループット、のそれぞれを計測したんだけど、そのときの結果およびリソースグラフは以下の通り。

メッセージ転送レート計測

目標msg/s bytes/msg msg/call call/sec 実績msg/s 実績Mbps
1000 100 50 20 971 1Mbps
4000 100 100 40 3924 4Mbps
11000 100 183 60 10147 10Mbps
16000 100 200 80 11222 11Mbps

目標16000msg/s におけるRPCあたりメッセージ数と目標RPCレートのバランス確認

目標msg/s bytes/msg msg/call call/sec 実績msg/s 実績Mbps
16000 100 50 320 10160 10Mbps
16000 100 100 160 10698 10Mbps
16000 100 200 80 11280 11Mbps
16000 100 400 40 11702 11Mbps
16000 100 800 20 11911 12Mbps

目標10000msg/s におけるメッセージサイズ変更によるデータ転送スループット計測

目標msg/s bytes/msg msg/call call/sec 実績msg/s 実績Mbps
10000 50 200 50 9753 6Mbps
10000 100 200 50 9739 10Mbps
10000 200 200 50 9683 18Mbps
10000 400 200 50 9530 34Mbps
10000 800 200 50 9136 62Mbps



(グラフはみっつ見えている長時間の山がそれぞれのテストに対応する。時間順序は上記に並べた順番で実行した。左端の小さな山は昼間にやった実験のもので関係ない。)

メッセージ転送スループットで 11000msg/s というのがひとまずの数値でデータ転送スループットはまだ上限見えないなーというあたり。ただしCPU使用率を見ると1コアをフルに使った数値(12.5%)に届いていないのが気になるといえば気になる。が、このあたりは結局翌朝には fluentd 0.10.7 が出てほとんど意味がなくなったのであまり考えないことにしよう。

ちょっと面白いのがRPCあたりのメッセージ数と目標RPCレートに関するデータ。かなり派手にバランスをいじったのに結局ほとんど変わってない。これはつまりThriftプロトコル上の問題(RPC、Thriftオブジェクトのパースの両方)よりも、fluent内部のメッセージ処理の方がはるかに重くてThriftプロトコルの負荷をどっち側に振るかはどうでもいい、ということだろうか。
fluentd内部ではメッセージ単位で Engine.emit() が呼ばれているはずなので、そこの重さが支配的だというならこのような挙動がなんとなく納得できる。

メッセージ転送スループット、データ転送スループット

ここ以降は fluentd 0.10.7 にアップデートしてからの結果。

メッセージ転送スループットについてどの程度が限界かを探るため fluentd の設定を以下のようにしてベンチマークをかけた。

  • in_scribe
    • detach_process 2
  • out_scribe
    • detach_thread 4

結果はこのような感じ。すべて60分ずつ走行した。計測値はメッセージ転送スループットの実績値。

目標msg/s bytes/msg msg/call call/sec 実績msg/s
4000 100 100 40 3860
8000 100 133 60 7734
16000 100 200 80 15578
24000 100 240 100 18093

RPCレートを上げていくとだいたい 18000 msg/s に達したところで限界がきた。前にやった 0.10.6 の数値 11000msg/s の1.5倍以上出ている。データ転送スループットではメッセージあたりのサイズが小さいので 20Mbps 程度しか出ていないので数値は省略。

またデータ転送スループットの計測を、目標メッセージ転送スループット 10000 msg/s と 15000 msg/s の2通りの走行で確認した。データ転送スループットの計測値はグラフから読みとったものなのでかなり大雑把*5。fluentdの設定は前のと同じ。これも60分ずつの走行にしてある。

目標msg/s bytes/msg msg/call call/sec 実績msg/s 実績Mbps
10000 200 200 50 9658 20Mbps
10000 400 200 50 9802 40Mbps
10000 800 200 50 9800 75Mbps
10000 1200 200 50 9711 100Mbps
15000 200 300 50 14562 28Mbps
15000 400 300 50 14696 55Mbps
15000 800 300 50 14583 100Mbps
15000 1200 300 50 13870 145Mbps

これを見る限り、10000msg/sに抑えている限りは、メッセージサイズに対してほぼリニアにデータ転送スループットが上がっていっている。対して15000msg/sを目標にするとメッセージサイズが1200bytesあたりで少々きつくなるようだ。とはいえ少しきつくなったかなくらいで、実際にやってみるとまだ上がりそうな気配がある。



(左側に見えているのは前日の試験分。いま見たいのは右側にみっつ見えている山のところ。)

メッセージ転送スループットについてはCPU1コアを使いきった付近(使いきる手前?)で限界を迎えていてそれ以上はどうにもならないように見える。Thriftの処理がボトルネックになっているならここで in_scribe のマルチプロセス化の効果がはっきり見えるはずだが、残念ながらそうなっていない。このような状況だとあんまり意味がなさそうだ。
とはいえ秒間18000メッセージというのはかなりいい数字だと思う。これくらい出てくれると現実的なノード数で処理できる予感がぐっと高まる。

データ転送スループット限界計測

結局前の計測でデータ転送スループットの上限が見えなかったので、各条件30分走行にして上限を探ってみた。前の試験でfluentd側のCPUコアを使いきれていなかったのが明らかだったので、設定は以下のように変更した。

  • in_scribe
    • detach_process 6
  • out_scribe
    • detach_thread 8

このfluentdに対し目標メッセージ転送スループットを 15000msg/s と 20000 msg/s の2通り、メッセージサイズもがつっと最大4800bytes/msgまで上げてみた。

目標msg/s bytes/msg msg/call call/sec 実績msg/s 実績Mbps
15000 600 300 50 14458 80Mbps
15000 1200 300 50 13692 150Mbps
15000 2400 300 50 11142 240Mbps
15000 4800 300 50 7783 340Mbps
20000 600 400 50 16211 90Mbps
20000 1200 400 50 13756 150Mbps
20000 2400 400 50 10856 230Mbps
20000 4800 400 50 7978 350Mbps

このときの各種グラフは以下の通り。(右端のふたつの山。)


このときbenchサーバ側のiowaitが出ていることから最終的な受け取り手のscribedがボトルネックになった可能性があるので、fluentd自体の処理性能としては実際にはもっとスループットが出たかもしれない。またこのときはメモリに動きが出た。やっぱり出力先が詰まってバッファに溜まったのか、単に扱うデータサイズが大きくなったから増えたのかまでは見ていない。まあ、特に問題ないレベル。

さすがにメッセージサイズを倍にするごとにメッセージ転送スループットが落ちていくが、そこまで急激に落ちるわけでもなく、結果としてデータ転送スループットは上がりつづけて350Mbpsを記録した。これはすごい。これだけ出ると普通の用途では充分と言っていいと思う。というかこれ以上出すとネットワークまわりを真面目に見る必要が近付いてくる。

とはいえCPUは結局前と同様にあまり使いきれたという感じでもない。有り体に言って1コアを使いきったあたり(12.5%)付近で限界を迎えているように見える。実は in/out のマルチプロセス・マルチスレッド化は今回のベンチマークの範囲では全く効果がなかったんじゃなかろうかとすら思える。うーむ。

結論

fluentdのメッセージ処理自体は現状CPU1コアに処理できる分がおそらく最大で、今回使ったサーバだとだいたい18000msg/secくらいが限界値のように感じる。現状扱っている(もしくは将来的に扱う見込みの)メッセージ数がこれを超える場合、1台のfluentdではさすがに処理しきれないということになってしまう……だろう、残念ながら。

ただしデータ転送スループット自体はかなりの量まで耐性があるようだし、1メッセージあたりのサイズを約4.8KBにしてもそれなりにスケールするようなので、全体的にメッセージ数が多い場合は送出元の側で複数メッセージをひとつのメッセージにパックしてからfluentdに送るといったハックをすると極めてよくスケールする。この戦略は大規模なfluentdクラスタを設計する予定があるなら、おそらく考えておいて損はない。自分はこの戦略をかなり極端にとるつもり。

入出力プラグインのマルチプロセス・マルチスレッド化については、プラグイン側でどの程度の処理をするかということにかかってくる。少なくともThriftプロトコルの処理くらいではあまり効果がないらしい。fluentdのコア側がボトルネックになっている気がする。0.10.6から0.10.7での高速化はそこが改善されたからだろう多分。
とはいえプラグイン側で重い処理をするケースもそれなりに考えられる*6ことから、将来的にこのオプションが意味無いということはまず無いと思う。

どうしても物理サーバ1台あたりのメッセージ転送スループットを稼ぎたい場合はfluentd自体を複数プロセス上げることが考えられる。そんなに非現実的な案でもないかもしれない。

全体的には現状 fluentd 0.10.7 にかなり満足した。ヘビーに使っても問題なさそうなパフォーマンスが出ていると思う。冗長化を考えても2〜3台のサーバがあればメッセージ転送とロードバランスはやれると思う。よっしゃよっしゃ。

*1:たとえば典型的な送信対象データの数倍〜数十倍

*2:RPC呼び出しおよびデータオブジェクトのパースにCPUを食うようなプロトコルの場合はこのような戦略により、最終的なデータ転送スループットを大幅に上げられる。

*3:転送対象のデータを圧縮するという手があるがいろいろとめんどくさいのであんまりやりたくない

*4:そもそも予備計測というより、やった時点では最新版 0.10.6 に対する本番計測だったんだけど……。

*5:逆に言うと、そんなに細かく数値とってもしょうがない

*6:そのためにプラグインを書きやすいfluentdを使うんだろうし