こんにちは。最近Fluentdのメンテナーになった福田です。
2023年4月17日にFluentdの最新版となる v1.16.1 をリリースしました。 2023年3月29日にリリースした v1.16.0 と合わせて、その内容をメンテナーの観点から解説します。
なお v1.16.0 では設定次第でFluentdが起動に失敗するという問題があるので、 v1.16.1 をご利用ください。
電源断などのシステム異常停止によるチャンクファイル破損への対応を強化
Output
プラグインに設定するバッファーとして、buf_fileやbuf_file_singleを設定されている方が多いのではないでしょうか。
これらはバッファーとしてファイルを利用することで、電源断などでFluentdが突然動作を停止してもデータが失われない、というメリットがあります。
しかし一方で、電源断などの障害によってファイルが壊れてしまう可能性もあります。
バッファーが扱う処理単位をチャンクと呼びますが、buf_file
やbuf_file_single
は1チャンクを1ファイルとして扱います1。
そのためバッファーが扱うファイルのことをチャンクファイルと呼びます(単にバッファーファイルと呼ぶこともあります)。
電源断などのシステム異常停止によってチャンクファイルが破損してしまったと思われる不具合が少なからず報告されてきました。 ファイルが破損してしまった以上、ソフトウェア側でできることは限られていますが、 v1.16.0 においていくつか改善を行いました。 その改善点について詳しく説明します。
ドキュメントとしては次をご覧ください。
起動時に検知した破損チャンクファイルをバックアップするように改良
多くのチャンクファイルは動作中に処理されますが、Fluentdがチャンクファイルを残した状態で動作を停止することもあります。 この場合、次回起動時に残っているチャンクファイルを読み込んで処理を継続します。
もし強制電源断などでシステムが異常停止した場合は、この起動時に残っているチャンクファイルが破損している可能性があります。 破損の仕方によって、破損を検知できて読み込みをあきらめることもあれば、破損を検知できずおかしな値のレコードとして読み込んでしまうこともあります2。
v1.16.0 から、この「破損を検知できて読み込みをあきらめたチャンクファイル」をバックアップ3するようになりました。 (v1.16.0より前は破棄されていました)
これにより、どのような破損だったのか、復旧できそうなデータはあるのか、などを後から調べることが可能になります。
チャンクファイルの破損についてより多くの情報をログに出力するように改良
チャンクファイル破損の厄介な点として次の2つがあります。
- 破損を検知できずに壊れたレコードとして読み込み、後続の処理で様々なエラーを引き起こす
- ロストしたレコードの特定が難しい
v1.16.0 では、これらの問題を改善するために以下の2つ点でログ情報を改善しました。
1つめは、電源断などの異常停止があった可能性とその際のチャンクファイルリストをログに記録する機能です。
flush_at_shutdownが有効なバッファーでは、Fluentdの停止時に全てのチャンクをフラッシュしようとします。 そのため、正常に停止した場合はチャンクファイルが残らないはずです。 もしFluentdの起動時にそのバッファーに関するチャンクファイルが残っていたならば、それは電源断などの異常停止があった可能性があり、すなわちチャンクファイルが破損したかもしれない、ということになります。
このようにflush_at_shutdown
が有効であるバッファーにおいて起動時に残っているチャンクファイルがあった場合には、そのファイルパスを警告ログとしてログに記録するように改良しました。
[warn]: #0 restoring buffer file:
path = /path/to/buffer.b5f9ceec53487306e9e926ea0b208d19b.log
例えば後続の処理でよくわからないエラーが発生してしまった場合、この警告ログが起動時に出ていれば、
「電源断などがあったのかもしれない。チャンクファイルが破損したことによって壊れたデータが流入したのではないか。」
のように原因の目処を付けることが可能になります。
また現実的には複数残ったチャンクファイルのうち、起動時に破損と検知できるファイルもあれば、検知できないファイルもある、という状況が多いと想定しています。 2つめの機能として、1つでも破損を検知できた場合には他のチャンクファイルも含めて次のように詳細な情報をログに記録するように改良しました。
[info]: #0 [test_id] Since a broken chunk file was found, it is possible
that other files remaining at the time of resuming were also broken.
Here is the list of the files.
[info]: #0 [test_id]
test/fluentd/buffer/buffer.b5f32716d7292f8138b36fd759abf7207.log:
created_at=2023-01-26 18:08:16 +0900
modified_at=2023-01-26 18:08:17 +0900
[info]: #0 [test_id]
/test/fluentd/buffer/buffer.b5f32716d734618fef772d3ae48fd577a.log:
created_at=2023-01-26 18:08:16 +0900
modified_at=2023-01-26 18:08:17 +0900
このように、破損を検知できなかったチャンクファイルについても、チャンクの生成日時と最終更新日時の情報も含めて記録します。 破損があった場合には、このログを確認することでデータがロストした可能性のある期間を絞り込み、リカバリーに役立ててください!
out_forward
: 複数のForwarderを介して転送するときに圧縮できなかった問題を修正
v1.16.1 で修正しました。
ログを収集したい各機器にFluentdを立てつつ、それらからログを集めるためのFluentdを別に立てる、ということがよくあると思います。 前者をForwarder、後者をAggregatorと呼びます。
Forwarderはin_tailなど必要に応じた様々なInput
プラグインでログを収集し、out_forwardでAggregatorにログを転送します。
Aggregatorはin_forwardでForwarderから転送されてくるログを受け取り、out_s3など必要に応じた様々なOutput
プラグインで最終的な送信先にデータを送信します。
ForwarderとAggregator間のデータ転送はout_forward
とin_forward
で行われます。
その際、out_forward
のcompress設定でgzip
を指定することで、そのデータを圧縮することができます。
今回の問題は、複数のForwarderを介して転送する場合に、中継のForwarderのout_forward
においてこの圧縮設定をすることができなかった、というものでした。
例えば次のようにForwarder1とForwarder2を介して転送を行うケースが該当します。
Forwarder1(out_forward) -> Forwarder2(in_forward, out_forward) -> Aggregator(in_forward)
このケースではForwarder1の転送を圧縮することは可能ですが、Forwarder2の転送を1と同時に圧縮することができませんでした。 Forwarder1と2の双方で圧縮設定をすると、次のようなエラーが発生してデータを転送できなかったのです。
[error]: #0 unexpected error on reading data host="..." port=... error_class=ArgumentError error="unknown keyword: :packer"
そのためForwarder2において圧縮をあきらめざるをえず、Forwarder1で圧縮をしてもForwarder2が解凍してしまう、という事態を招いていました。
v1.16.1 でこの問題を修正し、中継のForwarderにおいても圧縮設定が可能になりました。 これにより、データを圧縮したまま複数のFluentdを介して転送させることができるようになった、ということです。
セカンダリーの改善
out_secondary_file
: 警告ログ Use different plugin for secondary.
の抑制
v1.16.0 において、セカンダリーの設定に関する警告メッセージの挙動を改善しました。
セカンダリーとは、Output
プラグインでバッファーを用いる場合に可能な設定で、これによって何度も送信に失敗した4データを退避することができます。
セカンダリーを設定しない場合、このようなデータは失われます。
この設定にあたり、セカンダリーの出力に用いるOutput
プラグインの種類を指定することになります。
out_secondary_fileはセカンダリーとしてローカルファイルにデータを退避するためのプラグインになっており、
特別な理由がなければこれを設定します。
しかし以前からこの場合に次のような警告ログが発生していました。
[warn]: #0 Use different plugin for secondary.
Check the plugin works with primary like secondary_file
primary="Fluent::Plugin::FileOutput"
secondary="Fluent::Plugin::SecondaryFileOutput"
これは、
「プライマリー(本体のOutput
プラグイン)と異なるプラグインをセカンダリーに設定しているよ。(out_)secondary_fileのようにプライマリーと合わせて動作できるのかどうかをしっかり確認してね。」
という警告メッセージなわけですが、これがout_secondary_file
を設定しても発生しており、混乱を招いていました。
v1.16.0 においてこれを修正し、out_secondary_file
を設定している場合にはこの警告メッセージが発生しないようになりました。
out_secondary_file
: 書き込みの競合を修正
マルチワーカーやマルチスレッドで動作している場合に、out_secondary_fileの書き込み処理が競合する可能性がありました。 書き込みが競合すると、異なるファイルへ退避されるはずのデータが同じファイルに混じってしまうことがあります。 未確認ですが、運が悪いと一部のレコードが破損する可能性もあるかもしれません。
v1.16.0 にてこの問題を修正しました。 マルチワーカーやマルチスレッドで動作させる場合も安全にデータを退避することができます。
またこの問題の修正にあたり、マルチワーカーとマルチスレッドの双方に対応した排他機構をOutput
プラグインの共通機能として実装しました。
以前 Fluentd v1.15.1リリース -- プラグイン共通の排他機能の追加 の記事において、acquire_worker_lock
メソッドを紹介しました。
これは全プラグイン共通の機能として、ワーカー間の排他機能を提供するものでした。
しかし、Output
プラグインのwrite
メソッドなどは、マルチワーカーによる並列(pararell)処理だけでなく、バッファーがマルチスレッドで動作することによる並行(concurrent)処理についても配慮をする必要があります。
RubyではGVLのためマルチスレッドはpararellではなくてconcurrentで動作します。
そのため、マルチスレッド間の処理競合は発生しないように思うかもしれませんが、一概にそうとは言えません。
例えば、「まず出力先のユニークなパスを決定し、次にそのパスにファイルを作成する」という一連の処理について考えてみてください。
コードにすると、次のようにまずgenerate_path
関数でユニークなパスを決定し、次にそのパスにチャンクを出力する、という形になります。
def generate_path
i = 0
loop do
path = "output.#{i}.log"
return path unless File.exist?(path)
i += 1
end
end
def write(chunk)
path = generate_path
File.open(path, "ab") do |file|
chunk.write_to(file)
end
end
この場合、最初のステップ(generate_path
関数実行)時点でそのパスが存在しておらずユニークであったとしても、実際にチャンクを出力するまでの間に他のスレッドが同じパスに出力してしまうことはありえます。
この場合、別々のファイルに分けて出力したかったデータが、同じファイルに混ざってしまうことになります。
このように一連の処理を排他的に行うためには、マルチスレッド間での排他についても考えなくてはなりません。
特にOutput
プラグインのwrite
メソッドなどは、バッファーがマルチスレッドで動作することも考慮して設計する必要があります。
そのため v1.16.0 において、Output
プラグイン専用の機能として、ワーカー間に加えてスレッド間での排他機能を提供するsynchronize_path
メソッドを実装しました。
これを利用することにより、現在のワーカーかつ現在のスレッドに限って排他的に実行することができます。
今後のOutput
プラグインにおいて、排他的に実行したい処理はこのメソッドをぜひご利用ください。
synchronize_path
メソッドは次のように実装されています。
out_secondary_file
では、このメソッドを次のように利用しています。
v1.16.0 において起動に失敗することがある問題について
多くの改善が入った v1.16.0 ですが、ある条件を満たすとFluentdが起動に失敗するという大きな不具合がありました。 この不具合は v1.16.1 で修正済みであり、 v1.16.0 のみで発生します。
その条件とは「設定ファイルの一番最初に登場するセカンダリーの設定がout_secondary_fileではない」というものです。 この場合、以下のようなエラーが発生してFluentdが起動に失敗します。
/path/to/fluentd/lib/fluent/plugin/output.rb:429:in `configure': uninitialized constant Fluent::Plugin::Output::SecondaryFileOutput (NameError)
if (@secondary.class != SecondaryFileOutput) &&
^^^^^^^^^^^^^^^^^^^
v1.16.1 以降のバージョンをご利用ください。
その他
in_tcp
: send_keepalive_packet オプションを追加しました。in_tcp
: message_length_limit オプションを追加しました。out_forward
:require_ack_response
を有効化している場合、Fluentd停止時にackに関するエラーログが出力されることがある問題を修正しました。- Fluentdログ: 起動時の一部のログにログフォーマットが適用されていなかった問題を修正しました。
- Fluentdログ: 一部のSystem設定が反映されていなかった問題を修正しました。
- suppress_repeated_stacktrace
- ignore_same_log_interval (Supervisorのみ)
- Windows: 使用できないポートを起動時に開こうとして起動に失敗することがある問題を修正しました。
- WindowsのFluentdログ: ログローテートを有効にしている場合、fluent-ctlやRPCによるフラッシュやリロードによって誤ったログファイルがreopenされてしまうことがある問題を修正しました。
- Windows以外: SIGTERMを受信した際に、意図せずSIGDUMPファイルを出力していた問題を修正しました。
まとめ
今回は、Fluentdの最新版 v1.16.1 と v1.16.0 についての情報をお届けしました。
クリアコードはFluentdのサポートサービスを行っています。
今回主要なトピックとして紹介した以下の改善は、クリアコードがサポートしているお客様からのご相談に基づいて実施したものです。
- 電源断などのシステム異常停止によるチャンクファイル破損への対応を強化
out_forward
: 複数のForwarderを介して転送するときに圧縮できなかった問題を修正out_secondary_file
: 書き込みの競合を修正
クリアコードの理念は、フリーソフトウェアとビジネスの両立です5。
Fluentdはフリーソフトウェアであり、必要な機能や困る不具合がある場合はユーザー自身で修正することができます6。
クリアコードも、お客様のあるなしに関わらず、Fluentdというフリーソフトウェアの発展のため日々メンテナンスを行っています。
一方で、あるユーザーにとって必要な機能や困る不具合があったとしても、そのユーザーだけで解決できるとは限りません。 GitHubでissueを立てれば誰かがいつか解決してくれるかもしれませんが、それがいつになるかは分かりません。 クリアコードもそのようなissueに対応していますが、時間が限られるため、難しい問題については解決がだいぶ先になってしまうこともあります。
そしてクリアコードにとっても、フリーソフトウェアの発展に継続的に貢献していくために、会社を継続すること、すなわちビジネスが必要です。
お客様にクリアコードのFluentdサポートサービスをご利用いただくことで、 今回のようにお客様にとっての問題を解決しつつ、Fluentdをよりよいソフトウェアへと発展させることができます。
このようにクリアコードはフリーソフトウェアとビジネスを両立しているのです。
サービスについて詳しくはFluentdのサポートサービスをご覧いただき、お問い合わせフォームよりお気楽にお問い合わせください。
また、自分も仕事でFluentdの開発をしたい!という方は、クリアコードの採用情報をぜひご覧ください。
最新版を使ってみて何か気になる点があれば、ぜひGitHubで開発チームまでフィードバックをお寄せください。
-
厳密には1チャンクを2つのファイルで表現します。1つはデータ本体のファイル。もう1つはメタデータと呼ばれるそのチャンクの処理に必要なチャンクキーなどのデータを保存するファイルです。 ↩
-
後続の処理で様々なエラーを引き起こすので厄介です。破損をもっと検知しやすくするような仕組みの導入が期待されます。 ↩
-
バックアップ機能自体は以前からFluentdに備わっている機能です。 Handling Unrecoverable Errors ↩
-
ある程度送信に失敗しても、設定に基づいた回数や間隔でリトライを行います。リトライについては過去の記事で詳しく解説しているのでご覧ください。 Fluentd v1.14.6リリース -- リトライの修正と細かな動作改善 ↩
-
よく誤解されますが、フリーソフトウェアの「フリー」は「無料」ではなく「自由」を意味します。有料でもユーザーが自由に使えればフリーソフトウェアです。(Fluentdは無料で使えるフリーソフトウェアです) ↩