入出力プラグインをrubyで書けるのがじつにいい感じの fluentd がいい感じに見える。
ので使えるかどうか、使えるとしたらどれくらいのノードを用意すればいいのかについて考えるため、とりあえずベンチマークをとってみた。
結論
以下非常に長くなるので結論だけ書くと、大変使える感じ。現状だとほとんど何も考えずにデータ中継させても秒間1万メッセージ、100Mbpsくらいまでは処理できる。効率よくなるよう流す側も考えてやれば 300Mbps を超えるデータの転送に成功した。だいぶいい感じ。
なおこれは in_scribe および out_scribe を使用した場合で、開発者 @frsyuki によるとMessagePackでのデータ転送の場合はこの倍くらい出るらしい。
もちろんこれは右から左に流しただけなので現実にタグによるルーティングだとかログ中の時間による出力先変更だとかあれやこれやをやるともっと下がっていくのは確かだが、しかしそのへんの問題にはmaxでこれだけ出るのがわかっていればいくらでも対処のしようがあるので、充分実用に耐える数値だと思う。
以下詳細について。
ベンチマーク環境
ベンチマークは負荷をかける側およびかけられる側(fluentd)ともに以下のサーバを使った。
なおどちらも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に書くだけ)
- scribe_load.sh 実行
- fluentdサーバ
- fluentd 実行
- in_scribe で来たものをすべて out_scribe でbenchサーバで動作しているscribed宛に投げ返す
- fluentd 実行
両サーバの状況は 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回の処理あたりに送るデータ量(メッセージサイズ)を上げてやることで全体的なデータ転送スループットは向上する可能性が高い
ということで基本的な方針としては以下のようになる。
- メッセージ転送スループットについてどの程度が限界かをまず計測する
- このときメッセージサイズは比較的小さめ(ただし現実的な数値から乖離しない程度)にしておく
- またRPCレートとRPCあたりのメッセージ数をどの程度のバランスにするのがよいかも変えながら計測し、都合のいいあたりを探る
- データ転送スループットの最大値を計測する
ベンチマークをかける側の並列度が上がったときにどうなるかとかも気になる場合はチェックしておくといい。だいたいそんな感じ。
結果
ここから、表ではすべて以下のように書く。
- 目標メッセージ転送スループット
- 目標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台のサーバがあればメッセージ転送とロードバランスはやれると思う。よっしゃよっしゃ。