45395 - シコウサクゴ -

外れ値アラートを『連続N回』エスカレーションに切り替えてアラート疲労を解消した話

2026-05-04
AI駆動開発
AI駆動開発
監視
アラート
Python
運用設計
状態管理
Last updated:2026-05-07
10 Minutes
1922 Words

データ集計ジョブの結果に対して「直近 7 日平均と比べて 2 倍を超えたら警告」というシンプルな外れ値アラートを置いていました。

しかし、データの性質上 単発で大きく振れる日 が普通にあり、毎週のように誤検知の通知が飛んできて、本物の異常を見逃しかける状態に。これを 「連続 N 回」エスカレーション に切り替えた記録です。

単発閾値の限界

元のロジック

1
def check_anomaly(today_value: float, baseline: float) -> bool:
2
if today_value > baseline * 2:
3
send_alert(f"値が異常: {today_value}")
4
return True
5
return False

これだけだと、1 日でも閾値を超えたら通知が来ます。

何が起きるか

1
月曜 値=5.2 ← baseline=2.5、 2倍超え → 通知
2
火曜 値=2.1 ← 通常範囲、何も来ない
3
水曜 値=2.4 ← 通常範囲
4
...

月曜の通知を見て対応しても、原因がそれっきり消えていて何もできない。「連休明けの一時的な集中」「キャンペーン日」「センサーの一時的なノイズ」 のような、そもそも異常じゃない振れが大量に通知されます。

通知の 8 割が誤検知になり、人間が「またか」となって本物を見逃しはじめました。

設計:N 回連続で発生したら通知

考え方

1 回の閾値超過は「ノイズかも」、3 回連続なら「持続的に異常」です。

1
月曜 値=5.2 → トリガー(カウント開始)
2
火曜 値=4.8 → 連続 2 回目
3
水曜 値=5.0 → 連続 3 回目 → 通知!

火曜の値が 2.1 のような通常範囲だったらカウントをリセットします。

実装

連続発生のカウントを永続化する必要があります。プロセスが落ちても引き継げるよう、ファイルか DB に持たせます。

1
import json
2
from pathlib import Path
3
from datetime import date
4
5
STATE_FILE = Path("/var/state/anomaly_state.json")
6
CONSECUTIVE_THRESHOLD = 3
7
8
def load_state() -> dict:
9
if not STATE_FILE.exists():
10
return {"consecutive_days": 0, "last_anomaly_date": None}
11
return json.loads(STATE_FILE.read_text())
12
13
def save_state(state: dict):
14
STATE_FILE.write_text(json.dumps(state, default=str))
15
20 collapsed lines
16
def check_anomaly_with_consecutive(today_value: float, baseline: float, target_date: date):
17
state = load_state()
18
is_anomaly = today_value > baseline * 2
19
20
if is_anomaly:
21
state["consecutive_days"] += 1
22
state["last_anomaly_date"] = str(target_date)
23
24
if state["consecutive_days"] >= CONSECUTIVE_THRESHOLD:
25
send_alert(
26
f"値が {state['consecutive_days']} 日連続で異常: "
27
f"今日={today_value}, baseline={baseline}"
28
)
29
else:
30
# 連続ストリークが切れた
31
if state["consecutive_days"] >= CONSECUTIVE_THRESHOLD:
32
send_alert(f"異常状態が解消: {state['consecutive_days']} 日連続→正常へ")
33
state["consecutive_days"] = 0
34
35
save_state(state)

N の決め方

CONSECUTIVE_THRESHOLD を何にするかが設計の肝です。

N検知速度誤検知率適用ケース
1即時クリティカルな指標(停止検知)
2翌日即対応したいが誤検知も避けたい
32 日後持続的なトレンド異常
54 日後極低長期トレンド分析

私は最初 N=3 から始めて、誤検知が減ってちゃんと持続的な異常だけが上がってくるか見ました。3 日連続で異常が続く事象は、ほぼ確実に何かが起きています。

罠と対処

罠 1: 状態ファイルの永続化

/tmp 配下に置いていたら、再起動で消えていつもカウント 0 から始まる、という罠を踏みました。

1
# Bad
2
STATE_FILE = Path("/tmp/anomaly_state.json") # 再起動で消える
3
4
# Good
5
STATE_FILE = Path("/var/state/anomaly_state.json") # 永続パス

連続カウントが消えると、N=3 の意味がなくなります。再起動を超えて状態が残るパスに置くか、DB に入れます。

罠 2: ジョブが落ちると保存されない

1
state["consecutive_days"] += 1
2
send_alert(...) # ← ここで例外
3
save_state(state) # ← 保存されない

通知関数が外部 API なので失敗する可能性があります。保存を先にします。

1
state["consecutive_days"] += 1
2
save_state(state) # 先に保存
3
try:
4
send_alert(...)
5
except Exception:
6
logger.exception("alert failed but state saved")

通知に失敗しても、カウント自体は記録されているので、翌日の判定が正しく動きます。

罠 3: 「連続」の定義

「連続 3 日」とは何を指すのか?

  • 営業日のみ? → 土日を含めて連続でなくてもよい
  • 暦日 3 日? → 中 1 日空いていたらリセット?
  • データが取れた日 3 日連続? → 欠損日は無視?

ジョブが土日に走らない場合、最後の異常日と今日の日付の差を見る必要があります。

1
def is_consecutive(last_date: date, today: date, max_gap_days: int = 1) -> bool:
2
"""営業日的な連続を判定(土日を許容)"""
3
if last_date is None:
4
return False
5
gap = (today - last_date).days
6
return gap <= max_gap_days

私のジョブは平日のみなので、max_gap_days=3(月曜から見た金曜まで許容)にしました。

罠 4: 解消通知も忘れずに

「連続 3 日で異常通知」しただけで、解消したかどうかが追えないと、対応した人が「直ったの?」と確認に行く手間が発生します。

1
if state["consecutive_days"] >= CONSECUTIVE_THRESHOLD:
2
send_alert("異常状態が解消") # 必須
3
state["consecutive_days"] = 0

「異常検知」と「解消検知」をセットで実装すると、運用の手間が減ります。

拡張: ヒステリシス

「異常閾値で発動して、即座に正常範囲に戻ったら解消」だと、境界線で行ったり来たりする振動が起きます。発動と解消で別の閾値を使うと安定します。

1
TRIP_THRESHOLD = 2.0 # baseline の 2.0 倍を超えたら異常
2
RESET_THRESHOLD = 1.5 # baseline の 1.5 倍を下回ったら解消
3
4
def check(value, baseline, currently_tripped: bool) -> bool:
5
if not currently_tripped and value > baseline * TRIP_THRESHOLD:
6
return True
7
if currently_tripped and value < baseline * RESET_THRESHOLD:
8
return False
9
return currently_tripped # 状態維持

サーモスタットと同じ発想です。境界線で頻繁にトグルしないので、通知のノイズが減ります。

効果

指標Before(単発閾値)After(連続 3 回)
通知件数/月12〜18 件1〜3 件
誤検知率約 80%約 10%
対応所要時間通知ごとに 30 分通知ごとに 60 分(が、件数激減)
本物の見逃し月 0〜1 件月 0 件

通知 1 件あたりの所要時間は増えました(持続的異常なので調査が深い)が、月の合計時間は大幅に減少しました。

学び

1. 「単発で通知」は誤検知の温床

データに自然な振れがある以上、単発の閾値超過は通知すべきではないケースが多いです。持続性を判定軸に入れると、本物の異常だけが残ります。

2. 状態を持つ監視は永続化が必須

メモリだけで連続カウントを持つと、再起動で全部消えます。状態の置き場所を最初に決めます(ファイル or DB)。

3. 解消通知をセットで設計する

「発動」だけで「解消」がない監視は、対応者が状態を追えません。発動・解消をペアで実装します。

4. 「連続」の定義を明文化する

営業日 / 暦日 / データ取得日のどれか、コードコメントに必ず書きます。後で見ると思い出せません。

まとめ

観点設計方針
トリガー条件単発ではなく連続 N 回
N の値3 から始めて誤検知率で調整
状態永続化/var/state/ 等の永続パス、または DB
通知設計発動と解消のセット
振動対策発動と解消で別閾値(ヒステリシス)

「アラートが多い」と感じたとき、閾値を厳しくする方向ではなく 検知ロジックの構造を変えるアプローチが効くケースが多いです。

「連続 N 回」「ヒステリシス」「集約ウィンドウ」などの状態を持つ仕組みは実装の手間がかかりますが、長期的にはアラート疲労の根治につながります。

Article title:外れ値アラートを『連続N回』エスカレーションに切り替えてアラート疲労を解消した話
Article author:45395
Release time:2026-05-04

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

フィードバックを送る