たごもりすメモ

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

Fluentd v0.11 の設計案、について

このエントリは以下の記事およびFluentdの現状を受けてのものであり、Fluentdの実装についての知識を前提とします。

Fluentd v0.11 の設計案 — Gist

また言うまでもないことですが、自分の使いかただったらこうだといいなー、という程度のものであり、それ以上の意味もそれ以下の意味もありません。

では順にいってみよう。

内部ルーティングラベルの導入

実際にはこれだけでは問題は解決しなくて、forward先のFluentdで、forward元のFluentdではどこまで処理をやったメッセージなのか、ということを知る必要がある場合があるので、結局タグの操作もする必要がある。そうするとラベルの操作で解決するケースとタグの操作も行われているケースが混在することになり、状況はむしろ悪くなる。

じゃあどうするかというと悩ましいけど。じつは個人的には現行 v0.10 のタグ操作 + out_route(は勝手に入れた) であんまり困ってなかったりして。

ラベルを導入するとしたら、以下のどちらかになると嬉しい。

  • include 構文の拡張
    • include [@filtered] FILEPATH のように書くと、FILEPATH 内の設定はすべて @filtered ラベルに関するものとして扱う
include [@error] /etc/fluentd/error.conf
include [@sampled] /etc/fluentd/mongodump.conf
include [@unknowns] /etc/fluentd/mongodump.conf
  • match 構文の拡張
    • match hoge.** @filtered のように書くと @filtered ラベルのついたうちで hoge.** にマッチするものを処理する
<match accesslog.**>
 ...
</match>
<match accesslog.** @filtered>
 ...
</match>

相反する提案なのがアレだけど。

includeの拡張は、複数のラベルに対して同じ処理を適用したい場合(全部Mongoに突っ込む、など)に設定ファイルを再利用可能にするためのもの。これにより似たような設定がファイル内に大量にコピペされるのを防ぐことができる、かもしれない。そこまで必要な人がどれだけいるか分からないので、いらないかもしれない。
ただし副作用的に、ラベルつきメッセージの処理を他の設定ファイルに追い出すことで、メインの設定(デフォルトの空ラベルを処理する設定)の見通しをよくする、という効果はあるかも。メイン設定ファイルの末尾に include [@error] hoge ; include [@sampled] pos ; みたいに書いてあれば、どのようなマイナーケースについてはどのファイルを確認すればいいかも明らかになって良い。

match の拡張は設定ファイルの構文をよりシンプルに保つためのもの。 @label: という記法はありていに言ってスジが悪いと思う。ファイルをぱっと見たとき、どこからどこまでがどのラベルについての処理なのかが明らかでない。そのような構文を採用するべきではないと思う。

というわけで、上記ふたつの提案はどちらも設定ファイルをシンプルに保つためのもの。例外ケース(ラベルつきメッセージの処理)の記述量が多くなりそうなケースに対処したければ include 拡張が欲しいし、ラベルつきメッセージ処理をうんざりするほど繰り返して書く必要があまり無さそうなら match 拡張が欲しいんじゃないかと思う。
両方あっても、いちおう衝突はしないかな。ラベルつきincludeされたファイル中にラベルつきmatchが書かれてたらどうするか、の優先順位が決めてあれば、あるいは起動時エラーになれば大丈夫か。

Error#deterministic? の導入

Error#mortal? とか Error#fatal? でいいんじゃないのかな

エラーストリームの導入

ほぼ全面的に賛成

ProcessManagerの導入 / SocketManagerの導入

結局流量の多い input plugin を担当するFluentd Engineのプロセスがボトルネックになってあんまり状況が改善しないような気もする、けど、よくわからない。input pluginを並列化するところまでいかないとダメなんじゃないかと思う(httpdのpreforkみたいなモデル)。自分のところでは現状、流量の極端に多い input plugin (in_forward) とそこからのメッセージの配送を担当するEngineがボトルネックになっている印象がある。

プラグイン単位で並列化するより、Engine/input plugin のセットを並列化し input plugin へのデータの配送をロードバランスする prefork モデルの方がCPUコアに対してより良くスケールするモデルになると思う。これはSocketManagerの導入を前提にすれば作れるはず。output pluginは集約処理する可能性がある以上こういうことはできないから、これは元の設計案にある通りプロセスをまたいでルーティングするような方法をとるしかなさそう。

ここまで書いたけど、汎用の input plugin に求められることじゃないかー。たとえば in_dstat みたいなプラグインはこのような仕組みにはまったくそぐわない。
ので、データ配送(の受信側)だけプラグインの種類を分けるとか、あるいは何かプラグインごとにフラグを立てさせるとか、そういう形で扱いを分けたほうがいいかもしれない。in_forwardだけ特別扱いってのも考えたけど in_scribe とか in_flume とかもあるか。

まあ、ともあれ、プラグイン単位で別コアにするより、全メッセージを均等に各コアに割り振っていかないとCPUコアを効率よく使うのはたぶん無理だと思う。

またFluentdプロセスが細分化されると、そのそれぞれで(しかも違う増大ペースで)メモリリーク的な挙動を示したときに本当に収拾がつかなくなる。ので、MaxRequestsPerChild的な機構はおそらくほぼ必須になると思う。ないと運用はだいぶ厳しい。

到達保証の導入

おれイラネ。

……だけじゃなくて。ぱっと考えるに、Fluentdインスタンス内でのエラーについてはメッセージの改変があるから、その前後での正当性保証はたぶん非常に難しいと思う、というか無理なんじゃないかな。
現実的には out_forward から in_forward に渡るときにメッセージの件数もしくはチェックサムをとって伝達の確実性保証をするしかないんじゃないかなと思う。バッファチャンク単位とかで。そうなると forward じゃないプロトコルになりそうなので、専用のプラグインを作るしかないか。(あんまり真面目に考えてない)

個人的に欲しいもの: プラグイン向けイベントスケジューラ的な何か

プラグイン側で一定時間ごとにリアルタイム集計処理をしていたりする場合などに、現状だと別スレッドを立てるなりCool::Io あたりを自分で使うなりしないといけなくて、実装負荷も実行時負荷もあんまり馬鹿にならない気がする。
一定時間毎や一定の条件(emitされたメッセージ数など?)でプラグイン側の処理をkickしてくれる機構があるといいような気がする。そうすれば非同期処理エンジンはFluent::Engine側にひとつだけあればいい。kick条件とkickされるメソッドはプラグインの initialize (かstart)でEngineに対して登録するような形にする。

以上

こんなもんかな?