たごもりすメモ

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

Fluentd out_forward における最適化パラメータいくつかの話

Fluentdのデータをネットワーク経由で転送するための組み込みプラグイン out_forward には最適化のための設定がいろいろあるが、内部構造への理解がないとなかなか意味がわからなかったりするものも多い。ので、あんまりいじってる人はいないんじゃないかという気がする。
最近複数の転送先へのロードバランスを out_roundrobin ベースの方法から out_forward の機能を使った方法に切り替えてみたので、ついでにそのあたりについて書いてみる。

(おまけ) out_roundrobin と out_forward(のロードバランス)の違い

out_roundrobin は event stream の emit つまりFluentd内部における最小の配送処理単位ごとに配送先プラグイン(のインスタンス)を切り替える。可能な限り細かい単位で配送先をバラけさせたいときはこちらを使うとよい。0.10.25 時点では単純な均等ラウンドロビンしかできないが、設定毎に重みをつけるオプションが既に commit されているので次のリリースで多分含まれる。

out_forward で配送先 server を複数指定するとそれらを使用したロードバランスが行える。out_forward は内部バッファのflush時にそのバッファチャンクをどこに送るかを探すが、その際にロードバランス設定されたノードからひとつを選んで送る、というような動作となる。つまりロードバランスの単位は out_forward のbuffer chunk単位となる。

以前は*1なにか致命的に機能が足りないか何かで out_forward のロードバランス単位が大きすぎるから忌避していたんだけど、最近考えなおしてみたら特に問題がなさそうな状況だった、ので out_forward のロードバランスに依存するように変えてみた、という状況。設定もシンプルになることだし。

ノード数とスレッド数

num_threads

out_forward は複数スレッド化に対応したプラグイン*2で、そのスレッド数を指定するのが num_threads になる。これを増やすとI/O待ちについては並列処理してくれるので、配送データ量が多く、かつ複数ノードへのロードバランスが前提になる out_forward においては設定を考えたほうが良い。
num_threads を増やすデメリットは当然CPUを使う(コンテキストスイッチはそれなりに重い処理)のでCPUがかつかつなFluentdでは試さないほうがよい、が、今のところ自分の手元ではCPUよりもI/Oが詰まる方が怖いなーという感じ。*3

I/Oの並列化のみに効果があるので num_threads を配送先ノード数より多い数にしても意味はない。配送先ノード数と同じ数にするのは簡単だが、それはいくらなんでもやりすぎ。全ノードに一刻も休まず全力でデータを送り続けないといけないってことでしょ。

自分の手元でどうするかは考えたが、とりあえず配送先ノード数の半分にしてみた。これでもおそらく大分余裕がある数だが、CPUがきつくないなら多少以上多めにしておいても問題あるまい。おそらくノード数の 1/4 くらいでも大丈夫だとは思う(完全に勘)。

ということでこんな状態にした:

  • 配送ノード数: 56
  • num_threads: 28

このあと配送ノード数を 80 に増やしたが num_threads は 28 のままで、なにも問題なく動いてる。ノード数の 1/3 ってところか。

  • 配送ノード数: 80
  • num_threads: 28

buffer chunkあれこれ

out_forward によるラウンドロビンを考えるとき、このあたりのパラメータに注意する必要がある。極端な話 buffer chunk のflushが1時間に1回、1GB単位でしか行われなかったとしたら、ロードバランス用にいくらノード数を増やしていたとしても結局ひとつのノードに全部まとめてflushされて負荷が集中することになり、ロードバランスの意味がまったくない。

buffer_chunk_limit

buffer_chunk_limit は out_forward が内部で保持する buffer chunk の最大サイズを指定する。デフォルトは 8MB。
chunk flushは常にbuffer chunkまるごとに対して行われるため、ここで指定するサイズが out_forward がロードバランス先ノードに送信する単位の最大値となる。
たとえば1秒に1回くらいは各ノードに対してデータを送信したい、という場合、この buffer_chunk_limit が1秒間に送信できる*4サイズを超えていたら全体の動作が破綻する。送信先ノードの性能やノード間ネットワークの速度などをきちんと考えて指定する必要がある。

buffer_queue_limit

buffer_queue_limit は未送信の buffer chunk を out_forward 内で最大いくつまで保持するかの指定。デフォルトは64。
配送先ノードへの転送に時間がかかる場合は未送信buffer chunkがout_forward内に滞留することになる。out_forward はbuffer chunkをメモリ上に保持するため、無制限に保持しようとすればメモリが溢れて全体の動作がおかしくなる。それを抑止するため、万一の場合の保険としてこの最大値を指定する必要がある。
基本的に buffer chunk が滞留することはない*5はずなのであまり大きくする必要はない。が、あまり小さくしてもバースト的に転送量が増えたときに全く対応できなくなってそれはそれで問題がある。サーバ全体のメモリ量を圧迫しない程度に適当に指定しておけばよい。
唯一、buffer_chunk_limit と buffer_queue_limit をかけた総メモリ使用量がマシン全体で使えるメモリ量よりも小さくなるようにすること、という点のみ気をつけよう。

flush_interval

flush_interval はbuffer chunkをどのような時間間隔で flush するかの設定。buffer_chunk_limit に達していない程度のデータ量しか buffer chunk に入っていなくても、この時間が経過したら強制的に flush する。デフォルトは60秒。
ロードバランスを考えるときはできるだけ配送先ノードへのデータ送信の粒度が小さいほうがよい。その方が各ノードへの負荷配分が細かくなり、ノード間で負荷の偏りが発生しにくくなる。ということで、ロードバランス設定で out_forward を使う場合はこの数値は1秒で固定でしょう。

まとめ

out_forward でロードバランスを行う場合には buffer chunk がどのように溜まってどのようにflushされるかを理解しないと性能問題が出た場合などに対処が難しい。また処理するデータ量がどのくらいかということにも注意を払う必要がある。
あまり適当に設定すると低い頻度で巨大なデータが送信されたりしてロードバランスの役に立たないことがあるので、そのようなことが起きないように設定して快適なFluentdライフを楽しみましょう。

*1:あんまりよく覚えてないんだけど

*2:実はそういう機能をサポートする仕組み、というかインターフェイスがFluentd本体にある。対応しているプラグインはほとんど無いと思うけど。Ruby 1.9のマルチスレッドはCPUバウンドな処理を並列に走らせてはくれないので、I/Oがよっぽどヘビーにかかるところ以外では意味がない。

*3:ただしこれは配送サーバに限る。

*4:そして配送先ノードが処理できる

*5:そのような状況が慢性的に起きるということは全体の処理能力が足りてないので即座に改善するべき