BEMAロゴ

エンジニアの
成長を支援する
技術メディア

【ElastAlert2の罠】metric_agg_scriptとpercentile_rangeを併用する時の落とし穴と回避策

はじめに

案件先ではアプリケーションやロードバランサーのログを Elasticsearch に取り込み、Kibana 上でエラー率やレイテンシー等をダッシュボードで表示しています。
また、エラー率やレイテンシーに関して SLO(Service Level Objective)を定め、定期的に状況を確認するような運用を行っています。

一方で利用しているライセンスの都合上、SLO 違反を自動的に Slack に通知することができていませんでした。 そこで、ElastAlert2 を使用して SLO 違反を自動的に Slack に通知するような検証を行いました。

構成としては下記図のようなイメージとなります。

その過程で、metric_agg_scriptpercentile_range を併用する際に、意図したパーセンタイル値ではなく常に p1 の値で検証されてしまうという、落とし穴に直面しました。 本記事では、この問題の解決策、そして最終的に得た学びについて解説します。

ElastAlert2 の概要

ElastAlert2 は、Elasticsearch のデータから異常、スパイク、その他の関心のあるパターンを検知してアラートを通知するためのシンプルなフレームワークです。
Elasticsearch にクエリを投げ、その結果をルールと照合する仕組みで動作します。
なお、本記事で解説する事象は、ElastAlert2(v2.27.0)にて検証した内容に基づいています。

直面した問題

ElastAlert2 のルール設定

ロードバランサーのログは Logstash と Filebeat で Elasticsearch に取り込まれており、レイテンシーも request_time フィールドとして格納されています。
しかし Elasticsearch に取り込んだ際に、このフィールドが文字列として扱われていたため、Kibana 上では Scripted Field で型変換を行っていました。
Kibana のダッシュボードでは、この型変換をしたフィールドに対してレイテンシーのパーセンタイルを計算するように設定しています。

そのため ElastAlert2 においても、対象のフィールドの型変換を行った結果を元にパーセンタイルを計算する必要がありました。 そこで ElastAlert2 のルール設定では下記のように設定をしました。 (簡略化のため、一部の設定やスクリプトは省略しています)

name: Sample rule

type: metric_aggregation
index: sample_index
timestamp_field: @timestamp

metric_agg_key: request_time_num
metric_agg_script: 
  script:Double.parseDouble(doc['request_time'].value)

metric_agg_type: percentiles
percentile_range: 99
max_threshold: 3

alert:
  - slack

# 以下略

期待していた挙動

上記設定により ElastAlert2 に以下の挙動を期待していました

  1. metric_agg_script を適用して型変換を行い、その結果を利用してメトリックを算出する。

  2. percentile_range で指定したp99の値 が max_threshold の 3 秒を超えた場合アラートを発生させる。

実際の挙動

しかし、実際の挙動としては p99 の値を元に検証が行われておらず、適切にアラートを発生することが出来ませんでした。

また試しに percentile_range を 50 や 90 のように設定し、max_threshold も 0 にして常にアラートを発生させるように設定してみたところ、全ての percentile_range で同じ値が検証されていることが分かりました。

技術的な根本原因

ElastAlert2 のソースコードを調査したところ、metric_aggregation タイプで metric_agg_script を利用し、metric_agg_type として percentiles を指定した場合に、percentile_range のパラメータが Elasticsearch へのクエリ構築時に無視されてしまう実装となっていることを掴みました。

この場合、ElastAlert2 から Elasticsearch に問い合わせを行うクエリに取得対象のパーセンタイルが明示されず、Elasticsearch はデフォルトとして p1, p5, p25, p50, p75, p95, p99 のように複数のパーセンタイル値を返していました。

ElastAlert2 は返却された値の パーセンタイル値のリストの先頭要素( Elasticsearch のデフォルト応答では p1 )のみを利用して検証を行っていたため、percentile_range をいくつに指定したとしても、指定したパーセンタイル値ではなく常に p1 の値で検証されていたことが分かりました。

回避策

この問題の回避策として、metric_agg_script の設定値に percents パラメータを追加し、Elasticsearchに取得したいパーセンタイル値を明示的に伝えることで期待した動作となることを掴みました。

なお、回避策のYAMLにおいて percentile_range: 99 を残しているのは、ElastAlert2 のルールバリデーションでこのパラメータが必須とされているためです。Elasticsearch に送られるクエリでは percents: [99] が優先されます。

metric_agg_key: request_time_num
metric_agg_script:
  script: Double.parseDouble(doc['request_time'].value)
  percents: [99] # ★★★ この行を追加 ★★★

metric_agg_type: percentiles
percentile_range: 99 # この行がないとバリデーションエラーとなるため残す
max_threshold: 3

この回避策は、ElastAlert2 が内部で利用している Elasticsearch の Percentiles aggregation の仕様からも示唆を得ました。

OSS コミュニティへの貢献

上記の回避策により、一旦は自分の目的を達成することが出来ました。
一方で、この問題や回避策については ElastAlert2 のドキュメントやディスカッションにも記載がなかったため、他のユーザーの助けにもなればと考え、コミュニティに報告することにしました。
さらに、この問題の根本的な修正内容を検討し、Pull Request (PR) の作成も行いました。
結果としてPRは無事にマージされ、この問題は修正される見込みになります。

学び

これらのことから下記のことを学びました。

  1. ソースを読む
    ドキュメントが曖昧な場合や、原因を掴むためにはOSSのソースコードを読むことこそが最も確実な一次情報であり、デバッグを加速させることを実感しました。

  2. OSS への貢献
    OSS の問題を特定するだけでなく、それを受けて修正を検討し、コミュニティへの連絡やPR作成といった一連の流れを経験できました。
    初めてのコントリビュートで緊張しましたが無事マージされて良かったです。
    今後も機会があれば挑戦してみたいなと思います。

最後に

少しニッチな話になってしまいましたが、もし同じ事象で悩んでいる方の助けになれば幸いです。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

ykatsuno
ykatsuno
2020年に大手SIerに新卒入社。AI精度改善やWebアプリ開発/運用などに従事。2023年にメンバーズに入社後はクライアント先でSREとしてプロダクトの信頼性向上に向けた取り組みを行っている。
詳しく見る
ページトップへ戻る