45395 - シコウサクゴ -

アラート疲労の定量分析と段階的治療:通知を「減らす」のではなく「構造化する」

2026-04-12
AI駆動開発
AI駆動開発
監視
アラート
Slack
運用設計
Python
Last updated:2026-04-12
9 Minutes
1782 Words

データ処理パイプラインの監視を強化するたびに通知が増え、気づいたら1日に数十件のアラートが飛んでくる状態になっていました。重要なアラートも「またか」とスルーする——典型的なアラート疲労です。

「通知を減らす」ではなく「通知を構造化する」というアプローチで、この問題に取り組んだ記録です。

問題:アラートが多すぎて、何も見ない

アラート疲労の進行

1
Stage 1: 「通知が来た!すぐ確認しよう」
2
Stage 2: 「また同じやつか。後で見よう」
3
Stage 3: 「通知多すぎ。チャンネルをミュートしよう」
4
Stage 4: 「本当に重要なアラートも見逃す」

定量的な現状把握

まず1週間分の通知を集計しました。

1
総アラート数: 247件/週
2
- 即時対応が必要: 3件(1.2%)
3
- 当日中に確認: 12件(4.9%)
4
- 情報として有用: 45件(18.2%)
5
- 無意味(ノイズ): 187件(75.7%)

75%がノイズです。このノイズの中に埋もれた1.2%の重要アラートを拾い上げるのは、人間の注意力では不可能に近いでしょう。

ノイズの内訳

ノイズの種類件数/週原因
閾値が厳しすぎる68「念のため」で設定した低い閾値
一時的な遅延52ネットワーク遅延等で閾値超過→自動復旧
同一事象の重複411つの障害で複数ジョブが連鎖的にアラート
既知の事象26メンテナンス中のアラート等

解決策:4層のアラート構造化

「減らす」のではなく「層に分ける」方針です。すべての通知は記録しますが、人間の注意を引くレベルを分離します。

Layer 1: P0(即座に対応)— 通知先: 電話/SMS

1
p0_criteria:
2
- データ処理パイプライン全停止
3
- 直近24時間の出力がゼロ
4
- ディスク使用率 > 95%

週に0-1件です。この通知が来たら、何をしていても対応します。

Layer 2: P1(当日中に対応)— 通知先: Slack DM

1
p1_criteria:
2
- 特定ジョブの連続失敗(3回以上)
3
- データ鮮度が閾値を超過(自動復旧なし)
4
- 外部API連携のエラー率 > 10%

週に5-15件です。Slack DMで通知し、当日中に確認します。

Layer 3: P2(週次レビューで確認)— 通知先: Slackチャンネル(ミュート可)

1
p2_criteria:
2
- 閾値の一時的な超過(自動復旧あり)
3
- パフォーマンス低下(処理時間が通常の2倍以上)
4
- 非クリティカルなジョブの失敗

週に30-50件です。チャンネルに流しますが即時対応は不要です。週次レビューでトレンドを確認します。

Layer 4: ログのみ(通知しない)— 保存先: ログファイル

1
log_only:
2
- 正常実行の記録
3
- 閾値内の変動
4
- 自動復旧した一時的なエラー

残りすべてです。通知はしませんが、調査時に参照できるよう記録は残します。

実装:アラートルーター

1
from enum import Enum
2
from dataclasses import dataclass
3
4
class Severity(Enum):
5
P0 = "p0" # 即座に対応
6
P1 = "p1" # 当日中に対応
7
P2 = "p2" # 週次レビュー
8
LOG = "log" # ログのみ
9
10
@dataclass
11
class Alert:
12
job_name: str
13
message: str
14
severity: Severity
15
timestamp: str
18 collapsed lines
16
details: dict
17
18
def route_alert(alert: Alert):
19
"""重要度に応じて通知先を振り分ける"""
20
# 全アラートをログに記録
21
log_alert(alert)
22
23
match alert.severity:
24
case Severity.P0:
25
send_sms(alert)
26
send_slack_dm(alert)
27
log_alert(alert)
28
case Severity.P1:
29
send_slack_dm(alert)
30
case Severity.P2:
31
send_slack_channel(alert, channel="#alerts-low")
32
case Severity.LOG:
33
pass # ログのみ、通知なし

重要度の自動判定

手動で重要度を設定するのは面倒で、設定ミスの温床になります。ルールベースで自動判定します。

1
def classify_severity(job_name: str, error_type: str, context: dict) -> Severity:
2
"""エラーの種類とコンテキストから重要度を自動判定"""
3
4
# P0: 全面停止系
5
if context.get("all_jobs_affected"):
6
return Severity.P0
7
if context.get("zero_output_last_24h"):
8
return Severity.P0
9
if context.get("disk_usage_percent", 0) > 95:
10
return Severity.P0
11
12
# P1: 連続失敗、長時間のデータ欠損
13
if context.get("consecutive_failures", 0) >= 3:
14
return Severity.P1
15
if context.get("data_age_hours", 0) > context.get("max_age_hours", 24) * 1.5:
10 collapsed lines
16
return Severity.P1
17
18
# P2: 一時的な問題
19
if context.get("auto_recovered"):
20
return Severity.P2
21
if error_type in ("timeout", "rate_limit", "temporary_network"):
22
return Severity.P2
23
24
# デフォルト: P2
25
return Severity.P2

重複排除:連鎖アラートの抑制

1つの障害で10個のジョブがアラートを出すと、10件の通知が飛びます。これを抑制します。

時間窓による集約

1
from collections import defaultdict
2
from datetime import datetime, timedelta
3
4
# 5分間のウィンドウでアラートを集約
5
AGGREGATION_WINDOW = timedelta(minutes=5)
6
recent_alerts: dict[str, list[Alert]] = defaultdict(list)
7
8
def should_send(alert: Alert) -> bool:
9
"""同一カテゴリのアラートが短期間に集中していたら集約する"""
10
key = f"{alert.severity.value}:{alert.job_name}"
11
now = datetime.now()
12
13
# 古いアラートを削除
14
recent_alerts[key] = [
15
a for a in recent_alerts[key]
12 collapsed lines
16
if (now - datetime.fromisoformat(a.timestamp)) < AGGREGATION_WINDOW
17
]
18
19
if len(recent_alerts[key]) >= 3:
20
# 3件目以降は集約通知(「他N件の同様のアラート」)
21
if len(recent_alerts[key]) == 3:
22
send_summary(key, recent_alerts[key])
23
recent_alerts[key].append(alert)
24
return False
25
26
recent_alerts[key].append(alert)
27
return True

根本原因の特定支援

1
def detect_cascade(alerts: list[Alert]) -> str | None:
2
"""5分以内に3つ以上のジョブがアラートを出したら連鎖と判定"""
3
if len(alerts) < 3:
4
return None
5
6
job_names = {a.job_name for a in alerts}
7
if len(job_names) >= 3:
8
# 依存グラフから共通の上流を特定
9
common_upstream = find_common_upstream(job_names)
10
if common_upstream:
11
return f"根本原因の候補: {common_upstream} の障害が下流に波及"
12
return None

週次レビューの仕組み化

Layer 3(P2)のアラートは即時対応しませんが、週次で傾向を確認します。

1
def generate_weekly_report(alerts: list[Alert]) -> str:
2
"""週次アラートレポートを生成"""
3
total = len(alerts)
4
by_severity = Counter(a.severity.value for a in alerts)
5
by_job = Counter(a.job_name for a in alerts).most_common(5)
6
7
report = f"""
8
## 週次アラートレポート
9
10
**総数**: {total}
11
**内訳**: P0={by_severity.get('p0', 0)}, P1={by_severity.get('p1', 0)}, P2={by_severity.get('p2', 0)}
12
13
### アラート頻度 Top 5(要改善候補)
14
"""
15
for job, count in by_job:
3 collapsed lines
16
report += f"- {job}: {count}\n"
17
18
return report

Top 5の活用: 頻度の高いアラートは、閾値の見直し or 根本原因の修正対象です。「ノイズを減らす」のではなく「ノイズの原因を直す」という考え方です。

効果測定

Before → After

指標BeforeAfter
総アラート/週247247(総数は変わらない)
人間に通知/週24718(P0: 1, P1: 12, P2: 5件のサマリー)
重要アラートの見逃し月2-3件0件
アラート対応時間平均4時間P0: 15分以内, P1: 2時間以内

総数は減っていません。減らしたのは「人間の注意を引く回数」です。

閾値チューニングの継続

週次レポートのTop 5を毎週確認し、不要なアラートの閾値を調整し続けます。これにより、P2の件数は徐々に減少していきます。

まとめ

ステップアクション効果
1. 定量把握1週間分を集計「75%がノイズ」を数字で確認
2. 層の設計P0/P1/P2/Logの4層人間の注意資源を適切に配分
3. 自動判定ルールベースの重要度分類設定ミスを排除
4. 重複排除時間窓での集約連鎖アラートの抑制
5. 週次レビューTop 5の傾向分析ノイズの根本原因を特定

アラート疲労の解決策は「通知を減らす」ではありません。通知を構造化し、重要度に応じて人間の注意を制御することです。

全部記録し、重要なものだけ通知する。この原則を守れば、「通知が多すぎて見ない」と「通知が少なすぎて見逃す」の両方を避けられます。

Article title:アラート疲労の定量分析と段階的治療:通知を「減らす」のではなく「構造化する」
Article author:45395
Release time:2026-04-12

記事へのご質問・ご感想をお聞かせください

フィードバックを送る