対応不要なアラート発生を減らす Mackerel監視改善の実践

この記事では、Mackerel の不要なアラートを減らすために実施してきた監視設定の改善内容を紹介します。監視ルールへの変更内容、サービスの状態監視など、Mackerel SRE チームで行った実践的な改善事例を Terraform での設定を交えて詳しく解説します。

こんにちは。SRE の id:heleeen です。

監視は一度設定して終わりではなく、運用しながらより適切な状態に近づけていく必要があります。特に、アラートが発生したときに「このアラートは本当に発生する必要があったのか」「オオカミ少年になっていないか」と見直していくことが重要です。

Mackerel SRE チームでは、アラート当番が当番期間中のアラートをすべて確認し、アラートが発生したが対応する必要がなかった監視設定を改善する役割も担っています。そして、その変更内容は PWG(Performance Working Group)1 でアプリケーションエンジニアとも共有したり、SRE チーム外への申し送りとしてまとめたりしています。

この記事では、Terraform で管理している監視設定に対して、実際に行った具体的な改善事例をいくつかご紹介します。

障害状態の継続を通知する

AWS Lambda の Count メトリック2について、式による監視3を利用して関数の成功率を計算し監視していました。関数によっては実行数が少なく、数件のエラーが発生するたびにアラートが発生していましたが、エラーが数分継続する状態でなければアラート対応の必要はありませんでした。
これをホストメトリック監視に変更することで、エラーの継続を監視するようにしました。失敗の継続が検知でき、対応が必要なときにのみアラートを受け取ることができるようになりました。カウントなので割合の監視ではなくなりましたが、実行数が少ない場合はエラー件数のほうが誤検知を減らすことができています。

具体的な変更点として、これまでは式監視の movingAverage を利用して継続しない失敗でのアラートを避けようとしていたのですが、関数の実行件数が少ない場合にはアラートが発生していました。それをホストメトリック監視の max_check_attempts を利用して継続している状況の監視に変更しています。

resource "mackerel_monitor" "function_error_count" {
  name = "function_error_count"
  memo = "データ量が多いと処理が一時的に失敗する場合があります。5分程様子を見て処理に成功するかを確認してください"

-  expression {
-    expression = format("movingAverage(scale(offset(divide(role(%s,custom.lambda.count.errors),role(%s,custom.lambda.count.invocations)),-1.0),-100.0),5m)", "some:function_role", "some:function_role")
-    operator   = "<"
-    warning    = 99.9
-    critical   = 99.8
- }
+  host_metric {
+    metric             = "custom.lambda.count.errors"
+    operator           = ">"
+    warning            = 1
+    critical           = 3
+    duration           = 1
+    max_check_attempts = 5
+    scopes             = ["some:function_role"]
+  }
}

一時的なスパイクでアラートさせない

メトリックが一時的にスパイクするが、スパイクではアラートの発生を避けたい場合があります。例えば、Amazon Data Firehose の配信遅延やワーカーの処理時間などは、システムの負荷状況によって瞬間的に悪化することがありますが、すぐに復旧することも多いです。
このような一時的なスパイクに対してアラートを発報すると、対応が不要なノイズになってしまいかねません。

duration と max_check_attempts の調整

ホストメトリック監視で duration を短くし max_check_attempts を増やすことで、スパイクを無視しつつ継続的な異常を検知するように変更しました。以下は Firehose の配信遅延の継続を監視する例です。
これまでは duration によって平均した値を監視していましたが、継続性の監視をより確実に行うため max_check_attempts を利用するように修正しました。

resource "mackerel_monitor" "firehose_delivery_to_s3" {
  name = "firehose.service_data_freshness.backup_to_s3 is too high"
  memo = " Firehose に入ってから現在までの経過時間が伸びています。S3へデータが正常に配信できず Firehose で滞留している可能性があります。"

  notification_interval = 10

  host_metric {
    metric             = "custom.firehose.service_data_freshness.delivery_to_s3"
-   duration           = 3
-   max_check_attempts = 1
+   duration           = 1
+   max_check_attempts = 3
    operator           = ">"
    warning            = 550
    critical           = 600
    scopes             = ["service:firehose"]
  }
}

この設定により、1分間の平均値(Mackerel のメトリックは最小粒度が1分のため、平均値ですが実際にはその値自体が利用されます)が閾値を超えた状態が3回連続した場合にアラートが発報されるようになりました。監視ルールとしてのわかりやすさも改善されたと感じています。

同様の考え方は外形監視にも適用できます。

resource "mackerel_monitor" "mackerel_io_external_monitor" {
  name = "mackerel.io external monitoring"
  memo = "https://mackerel.io への外形監視です"

  notification_interval = 10

  external {
    url                    = "https://mackerel.io/"
    method                 = "GET"
    service                = "application"
-   response_time_duration = 3
+   response_time_duration = 1
    response_time_warning  = 15000 # 15sec
    response_time_critical = 15000 # 15sec

+   max_check_attempts = 3
    follow_redirect    = true

    certification_expiration_critical = 30
    certification_expiration_warning  = 57

    skip_certificate_verification = false
  }
}

avg_over_time による平滑化

クエリ監視では max_check_attempts のような設定はないため、PromQL の avg_over_time 関数を利用し、一定期間の平均値を算出することでスパイクの影響を抑制できます。以下はワーカーの処理時間を監視する例です。

resource "mackerel_monitor" "worker_duration" {
  name = "worker duration monitoring"
  memo = "worker の処理に時間がかかっています。継続する場合は runbook にしたがってスケールアップしてください"

  notification_interval = 10

  query {
-   query    = "max(messaging.process.duration.p95{service.name=\"worker\", service.namespace=\"application\"})"
+   query    = "max(avg_over_time(messaging.process.duration.p95{service.name=\"worker\", service.namespace=\"application\"}[5m]))"
    legend   = "duration"
    operator = ">"
    warning  = "3"
    critical = "5"
  }
}

5分間の平均値を監視することで、瞬間的な遅延ではアラートが発報しにくくなりました。

バッチの成功率ではなく処理結果を監視する

日次バッチの監視として、Lambda の実行成功率を監視していました。しかし、バッチが成功したかどうかよりも、バッチの処理結果としてデータが正しく作成されているかを監視するほうが本質的だと考えています。

例えばデータ削除バッチでは、バッチのステータスとして成功状態になっていてもデータが確実に削除されているとは言い切れません。そのため、バッチの成功率ではなく、データの削除状況を監視するように変更しました。

OpenTelemetry コレクターで状態をメトリック化する

ここでは例としてイベントログの削除バッチを取り上げます。OpenTelemetry コレクターの SQL Query Receiver を利用して、データベースに対してクエリを実行し、その結果をメトリックとして送信できます。以下は、イベントログの削除状況を監視するためのクエリの例です。

OpenTelemetry コレクターの設定例

receivers:
  sqlquery:
    driver: postgres
    datasource: "host=localhost port=5432 user=postgres password=postgres sslmode=disable"
    queries:
      - sql: "select count(*) as count from event_logs where created_at < now() - interval '90 days'"
        metrics:
          - metric_name: event_logs.current
            value_column: "count"

このクエリでは90日より古いイベントログ数を取得しています。このメトリックを Mackerel へ送信しその変動を見ることで、イベントログ数が把握できバッチによる定期的な削除が行われているかを監視できるようになります。

状態に対する監視ルールの作成

Lambda の成功率を監視していた監視ルールを、この event_logs.current メトリックを監視するように設定します。

resource "mackerel_monitor" "event_log_count" {
  name = "event log count"
  memo = "イベントログが増加しています。削除バッチの実行状況を確認してください。"

  notification_interval = 10

  query {
    query    = "event_logs.current"
    operator = ">"

    warning  = "100"
    critical = "150"
  }
}

この変更により、データ削除が実際に行われているかを監視できるようになりました。多少バッチが失敗してもそのうち成功すればアラートは発報されず、本当に問題が起きている場合のみアラートが届くようになっています。

アラート名で系列を識別できるようにする

クエリ監視では、PromQL のラベルによって複数の系列が返されることがあります。どの系列でアラートが発生したかを知るために、アラート名にラベルの値を含めるようにしました。

Mackerel のクエリ監視では、テンプレート構文4を利用して、アラート名にラベルの値を埋め込むことができます。以下は OpenTelemetry コレクターの Gateway で拒否されたスパン数を監視する例です。

resource "mackerel_monitor" "gateway_refused_spans" {
- name = "gateway: refused spans"
+ name = "gateway: refused spans {{receiver}}_receiver"
  memo = "gateway へのトレース送信で client にエラーが発生しています。gateway のスケールアップを検討してください。"

  query {
    query    = "increase(otelcol_receiver_refused_spans{service.name=\"gateway\"}[3m])"
    legend   = "{{receiver}}"
    operator = ">"
    critical = "0"
  }
}

この設定により、例えば receiver=otlp でアラートが発生した場合はアラート名が gateway: refused spans otlp_receiver となり、どのレシーバーで問題が起きているかが一目でわかるようになりました。

遅延時間によって監視を安定化させる

Mackerel には、式監視とクエリ監視で過去のデータポイントを遡って評価する「監視する式が安定するまでの遅延時間」という設定があります5。このパラメータを設定すると、データポイントの投稿が遅延した場合でも、過去にさかのぼって評価することで正しくアラートを発報できます。

resource "mackerel_monitor" "function_success_rate" {
  name = "function_success_rate"

  notification_interval = 0

  expression {
    expression = format("movingAverage(scale(offset(divide(role(%s,custom.lambda.count.errors),role(%s,custom.lambda.count.invocations)),-1.0),-100.0),5m)", "some:function_role", "some:function_role")
    operator   = "<"
    warning    = 99.9
    critical   = 99.8

+   evaluate_backward_minutes = 7
  }
}

クラウドインテグレーションのメトリックを式監視で扱うと、投稿と判定のタイミングの都合により閾値に達しないのにアラートが発生する場合がありましたが、evaluate_backward_minutes を設定することで、遅延したメトリックも含めて正しく監視できるようになり、アラートの精度が向上しました。

まとめ

この記事では、Mackerel SRE チームで実際に行った監視設定の改善事例をご紹介しました。

それぞれの改善によって、アラートの精度が上がり運用負荷が下がったと感じています。今後も監視設定の改善を続け、適切な監視状態を実現していきたいです。

この記事が、皆さんの監視設定改善の参考になれば幸いです。

もう「なんか遅い」で悩まない!開発者のためのAPM入門

アプリケーションを開発・運用していると、特定の処理が遅い、リクエストごとに応答時間がばらつくなど、「なんか遅い」と感じる場面があります。本資料では、こうした「なんか遅い」と感じる状況に対して、どこから調べればよいのか、何を手がかりにすればよいのかという観点から、APM(アプリケーションパフォーマンスモニタリング)が調査の進め方をどう変えるのかを解説します。

ダウンロードはこちら