ローリングz-score — 直近N日の動的基準で異常を測り、当日除外と最小期間で誤検知を防ぐ
z-score は全期間の固定平均・SDを基準にしました。しかし系列にトレンドや水準変化があると固定基準はすぐ陳腐化します。実務では「直近N日だけ」を基準にする移動z-score(rolling z)が定番です。鍵は2つ——当日を基準から除外すること(自分自身で自分を評価しない=leakage回避)と、基準が十分に貯まるまで判定を保留すること(min_periods)です。
AVG(amount) OVER (ORDER BY dt ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) -- 「3 PRECEDING AND 1 PRECEDING」= 当日を含めず、直前3日だけを基準にする -- これが「... AND CURRENT ROW」だと当日(=評価対象)が基準に混入してしまう
CURRENT ROW ではなく 1 PRECEDING にすると、当日の値は基準計算から外れます。異常な当日値が自分の基準平均・SDを押し上げて自分を正常に見せてしまう事故を防げます(フレーム指定の応用)。sensor_readings から、直前3日(当日除外)の移動平均 roll_avg・移動標準偏差 roll_std を求め、z =(amount − roll_avg)/roll_std を計算してください。ただし基準に使えた過去日数 n_base が3未満の行は判定保留('warmup')とし、それ以外は |z|≥2 で 'anomaly'、それ未満を 'normal' とします。出力列は dt, amount, roll_avg, roll_std, z_score, status、dt 昇順。平均・SD・z は小数第2位まで。
| dt | amount |
|---|---|
| 2024-01-01 | 100 |
| 2024-01-02 | 96 |
| 2024-01-03 | 104 |
| 2024-01-04 | 100 |
| 2024-01-05 | 96 |
| 2024-01-06 | 200 |
| 2024-01-07 | 100 |
| 2024-01-08 | 96 |
| 2024-01-09 | 104 |
| dt | amount | roll_avg | roll_std | z_score | status |
|---|---|---|---|---|---|
| 2024-01-01 | 100 | NULL | NULL | NULL | warmup |
| 2024-01-02 | 96 | 100.00 | NULL | NULL | warmup |
| 2024-01-03 | 104 | 98.00 | 2.83 | 2.12 | warmup |
| 2024-01-04 | 100 | 100.00 | 4.00 | 0.00 | normal |
| 2024-01-05 | 96 | 100.00 | 4.00 | -1.00 | normal |
| 2024-01-06 | 200 | 100.00 | 4.00 | 25.00 | anomaly |
| 2024-01-07 | 100 | 132.00 | 58.92 | -0.54 | normal |
| 2024-01-08 | 96 | 132.00 | 58.92 | -0.61 | normal |
| 2024-01-09 | 104 | 132.00 | 58.92 | -0.48 | normal |
連続異常の検知 — Gaps & Islands で『N日連続の異常』だけを束ねて拾う
単発の異常(点異常)と、「異常が何日も連続した=持続的な障害」は意味が違います。後者こそ本当に対応すべきインシデントです。連続する行を1つの塊(島=island)として束ねる定番技法が Gaps & Islands。核心は「連番との差分は、連続している間だけ一定になる」という性質です。
-- 異常日だけを日付順に並べ、連番(rn)を振ると… dt: 01-05 01-06 01-07 01-10 01-11 rn: 1 2 3 4 5 dt-rn: 01-04 01-04 01-04 01-06 01-06 ← 連続する塊ごとに同じ値!
GROUP BY すれば、連続ランごとに集約できます(ROW_NUMBER と GROUP BY/HAVING の合わせ技)。service_health(日次のエラー率)から、error_rate > 5 を異常日と定義し、異常日が3日以上連続したラン(持続的障害)だけを抽出してください。各ランについて 開始日 start_dt・終了日 end_dt・連続日数 run_days・期間中の最大エラー率 peak_error を求め、start_dt 昇順で返してください。
| dt | error_rate |
|---|---|
| 2024-01-01 | 2 |
| 2024-01-02 | 8 |
| 2024-01-03 | 3 |
| 2024-01-04 | 2 |
| 2024-01-05 | 9 |
| 2024-01-06 | 7 |
| 2024-01-07 | 6 |
| 2024-01-08 | 1 |
| 2024-01-09 | 2 |
| 2024-01-10 | 10 |
| 2024-01-11 | 12 |
| 2024-01-12 | 3 |
| start_dt | end_dt | run_days | peak_error |
|---|---|---|---|
| 2024-01-05 | 2024-01-07 | 3 | 9 |
季節性ベースライン — LAG(amount, 7) で『前週の同じ曜日』と比べ周期の罠を回避する
多くの指標は曜日の周期(週次季節性)を持ちます。平日は低く週末は高い、のような。これを「前日比」で監視すると、毎週末や毎週月曜に正常な周期変動が異常として誤報されます。対策は単純で、比較相手を「昨日」ではなく「1週間前の同じ曜日」にすること。LAG(amount, 7) で7行前を引いてくるだけです。
LAG(amount, 7) OVER (ORDER BY dt) -- 7日前(前週の同じ曜日)の値 LAG(amount) OVER (ORDER BY dt) -- 第2引数省略時は1(=前日) -- オフセットを変えるだけで「比較の基準」を昨日↔前週同曜日に切替えられる
LAG(col, n) は現在行からn行前の値を返します(既定 n=1)。日次データを日付順に並べれば 7行前=前週同曜日。先頭7日は遡る相手が無く NULL になるため、判定対象から外す(no_baseline)必要があります。daily_traffic(14日分・週末は高水準の週次季節性あり)から、各日の値を「7日前(前週同曜日)」と比較し、週次変化率 wow_pct =(amount − base_7d)/base_7d×100 を計算してください。7日前が無い先頭7日は 'no_baseline'、|wow_pct| が 50% を超えたら 'anomaly'、それ以外は 'normal' の status を付与。出力列は dt, amount, base_7d, wow_pct, status、dt 昇順。pct は小数第1位まで。
| dt | amount |
|---|---|
| 2024-01-01 | 100 |
| 2024-01-02 | 110 |
| 2024-01-03 | 120 |
| 2024-01-04 | 130 |
| 2024-01-05 | 140 |
| 2024-01-06 | 300 |
| 2024-01-07 | 310 |
| 2024-01-08 | 105 |
| 2024-01-09 | 115 |
| 2024-01-10 | 125 |
| 2024-01-11 | 135 |
| 2024-01-12 | 500 |
| 2024-01-13 | 305 |
| 2024-01-14 | 315 |
| dt | amount | base_7d | wow_pct | status |
|---|---|---|---|---|
| 2024-01-01 | 100 | NULL | NULL | no_baseline |
| 2024-01-02 | 110 | NULL | NULL | no_baseline |
| 2024-01-03 | 120 | NULL | NULL | no_baseline |
| 2024-01-04 | 130 | NULL | NULL | no_baseline |
| 2024-01-05 | 140 | NULL | NULL | no_baseline |
| 2024-01-06 | 300 | NULL | NULL | no_baseline |
| 2024-01-07 | 310 | NULL | NULL | no_baseline |
| 2024-01-08 | 105 | 100 | 5.0 | normal |
| 2024-01-09 | 115 | 110 | 4.5 | normal |
| 2024-01-10 | 125 | 120 | 4.2 | normal |
| 2024-01-11 | 135 | 130 | 3.8 | normal |
| 2024-01-12 | 500 | 140 | 257.1 | anomaly |
| 2024-01-13 | 305 | 300 | 1.7 | normal |
| 2024-01-14 | 315 | 310 | 1.6 | normal |
複合シグナルの重大度判定 — 水準と急変の2軸を組み合わせCASEで深刻度を分類する
実務のアラートは「異常か否か」の2値では足りません。複数のシグナルを組み合わせ、深刻度(severity)でランク分けするのが定番です。本問は2つの独立した観点——水準(値が高いか)と急変(前日から急に変わったか)——を掛け合わせ、4段階に分類します。鍵は CASE の分岐の順序と、真偽値(boolean)をそのまま条件に使う書き方です。
CASE WHEN impact_high AND urgency_high THEN 'BLOCKER' -- 影響・緊急度とも高い WHEN urgency_high THEN 'URGENT' -- 緊急度だけ高い WHEN impact_high THEN 'REVIEW' -- 影響だけ高い ELSE 'NORMAL' END
metric_stream から、2つのシグナルを計算してください:水準シグナル lvl_hi =(amount > 200) と 急変シグナル jump_hi =(amount − 前日値 ≥ 100)。これらを CASE で組み合わせ、両方真→'CRITICAL'/水準のみ→'WARNING'/急変のみ→'WATCH'/どちらも偽→'OK' の severity を付与します。出力列は dt, amount, delta, severity、dt 昇順(delta = amount − 前日値)。
| dt | amount |
|---|---|
| 2024-01-01 | 100 |
| 2024-01-02 | 105 |
| 2024-01-03 | 215 |
| 2024-01-04 | 220 |
| 2024-01-05 | 120 |
| 2024-01-06 | 235 |
| 2024-01-07 | 90 |
| 2024-01-08 | 195 |
| dt | amount | delta | severity |
|---|---|---|---|
| 2024-01-01 | 100 | NULL | OK |
| 2024-01-02 | 105 | +5 | OK |
| 2024-01-03 | 215 | +110 | CRITICAL |
| 2024-01-04 | 220 | +5 | WARNING |
| 2024-01-05 | 120 | -100 | OK |
| 2024-01-06 | 235 | +115 | CRITICAL |
| 2024-01-07 | 90 | -145 | OK |
| 2024-01-08 | 195 | +105 | WATCH |
アラートサマリの集約 — 多段 FILTER と GROUP BY ROLLUP で『カテゴリ別+全体』を一枚に【総まとめ】
応用編の締めは運用に渡す『アラートサマリ』の作成です。基礎編の COUNT(*) FILTER を発展させ、重大度ごとに複数の FILTER 列を並べて深刻度別件数を一度に数え、さらに GROUP BY ROLLUP でカテゴリ別の明細と全体の総計を同じ結果に同居させます。1クエリでダッシュボードの一枚が完成します。
COUNT(*) FILTER (WHERE amount >= 300) -- critical 件数 COUNT(*) FILTER (WHERE amount >= 200 AND amount < 300) -- warning 件数 GROUP BY ROLLUP (category) -- カテゴリ別の各行 + 全体の総計行(category=NULL) を生成
GROUP BY ROLLUP (category) は通常のカテゴリ別グループに加え、全カテゴリを束ねた総計行を自動で1行追加します。その総計行では category が NULL になるので、COALESCE(category,'(ALL)') でラベル化し、GROUPING(category) で並び順を制御します。event_log から、amount>=300 を critical、200≤amount<300 を warning と定義し、カテゴリ別に 総件数・critical件数・warning件数・critical率% を集計してください。さらに ROLLUP で全カテゴリの総計行も同じ結果に含め、総計行のラベルは (ALL) とします。出力列は category, total_events, critical, warning, crit_pct、カテゴリ行を category 昇順、総計行を最後に。pct は小数第1位まで。
| category | dt | amount |
|---|---|---|
| api | 2024-01-01 | 350 |
| api | 2024-01-02 | 120 |
| api | 2024-01-03 | 130 |
| api | 2024-01-04 | 95 |
| auth | 2024-01-01 | 260 |
| auth | 2024-01-02 | 280 |
| auth | 2024-01-03 | 240 |
| auth | 2024-01-04 | 90 |
| web | 2024-01-01 | 100 |
| web | 2024-01-02 | 250 |
| web | 2024-01-03 | 300 |
| web | 2024-01-04 | 320 |
| category | total_events | critical | warning | crit_pct |
|---|---|---|---|---|
| api | 4 | 1 | 0 | 25.0 |
| auth | 4 | 0 | 3 | 0.0 |
| web | 4 | 2 | 1 | 50.0 |
| (ALL) | 12 | 3 | 4 | 25.0 |