fluentdのBufferedOutputプラグインを書くときの注意点とか
fluetndのBufferedOutput系プラグインは、書き込みを適当なchunkにまとめて処理をしつつ書き込みに失敗したらよしなにリトライしてくれる便利なやつです。ただ実際にBufferedOutputプラグインを自分が書くとなった時に挙動をちゃんと理解していなかったのでまとめ。
まとめを先に書くと以下。
- 書き込み処理である
write
に失敗した場合は例外を投げる- 正しくリトライ処理を行うため
- 書き込みの処理内容はatomicである必要がある
- 中途半端に書き込まれたりして結果としてデータが重複したりしないように
- 一度に書き込めるサイズを調整するために
buffer_chunk_limit
等を調整する
Bufferedプラグインのリトライ処理に関してはBufferプラグインの概要に以下のように書かれています。
もしチャンクの書き出しに失敗した場合、チャンクはキューに残され、Fluentdは数秒後(retry_wait)に書き出しを再試行します。
ただ、実装上何をもって「失敗」とするのかがはっきりとせず、そこに対して明言されている資料も見つかりませんでした。
[追記] オフィシャルに解説追加していただきました!
答えを言うと、これはchunkを処理するwrite
メソッドが例外を投げれば失敗と判断するようになっています。例外が投げられた場合、chunkはキューから削除されず、しばらくしてからリトライされます。
リトライ処理の際には、前回に失敗したものと同じchunkが再度write
に渡ってきます。つまり、前回失敗した際に中途半端に書き込みなどが行われていると、データが重複して書かれてしまうなどの危険性があります。なので、write
の処理はatomic、つまり、完全に成功するか失敗するかのどちらかである必要があります。
さて、write
の処理をatomicにする必要があるのであれば、write
にわたってくるchunkの最大サイズを適切に調整する必要があります。chunkのサイズの上限はbuffer_chunk_limit
というパラメータで調整できますが、特に設定がない場合は8MBになっています。また、これはMessagePackのサイズなので、テキストデータなどに変換した際にはもっと大きくなることに注意が必要です。
この8MBというサイズは例えば書き込み先がWebAPIである場合、一度に投げる量としては大きすぎる場合も多いでしょう。そして処理をatomicにするためには一度に投げる必要があります。そういう時にbuffer_chunk_limit
を適切に調整してやる必要があります。
BufferedOutputプラグインには書き込みを調整するためのオプションが色々ありますが、それに関してはBufferedOutput pluginの代表的なoptionについての内容が非常に参考になりました。
fluent-plugin-mackerelがその辺りのケアがされていないところがあったので、修正したものが0.0.9としてリリースされています。これで万が一Mackerelのサーバーが調子が悪い時があっても、適切にfluentd側でバッファをして再送処理を行ってくれるようになりました。
ちなみにbuffer上のchunkは一度作られると明示的に分割する処理を挟まない限り再度分割されることは無いので、fluentdを多段でつないでいる場合に上流でサイズの大きなchunkが作られてしまうと下流で小さなbuffer_chunk_limit
を設定してあっても意図せず大きなサイズのchunkが渡ってきてしまう可能性があるので注意が必要です。その辺りの話は多段fluentd + mongodb のハマリ所に書かれています。
この辺の話はfluentd本体や先行pluginの実装を読んで把握したのですが、本当に正しいか確信できなかったので、データ転送ミドルウェア勉強会の時に@tagomorisさんに聞いたらそれで合ってるということだったので、まとめてみました。