緑ランプなのに数字が違う
一言でいうと
パイプラインの成功/失敗は、データの正しい/誤りとは別の軸です。品質を測らなければ、静かに空になったテーブルの上で、ダッシュボードが緑ランプで点灯しています。
なぜ必要なのか
月曜の朝の会議で、売上が前週比40%落ちたという報告が出ます。営業チームが大騒ぎになります。1日中原因を探して、午後に明らかになります。上流システムが土曜日にファイルを送っておらず、パイプラインは空のファイルを正常に処理して、0件をロードしていたのです。
パイプラインは失敗していませんでした。エラーもなく、リトライもなく、アラートもありませんでした。問題は、何も起きなかったという事実を、誰も知らなかったことです。
品質の4つの軸
| 軸 | 問い | 測定 |
|---|---|---|
| 鮮度(freshness) | データがいつのものか | 最新レコードの時刻と現在との差 |
| 完全性(completeness) | あるべき量があるか | 件数、NULLの比率 |
| 一意性(uniqueness) | 重複がないか | キーごとの重複件数 |
| 有効性(validity) | 値がルールを守っているか | 範囲・形式・参照整合性 |
それぞれをクエリ1行で測れます。
-- 신선도: 마지막 데이터가 몇 분 전인가
SELECT extract(epoch from now() - max(event_at))/60 AS lag_min FROM events;
-- 완전성: 어제 건수가 지난 7일 평균 대비 얼마인가
WITH d AS (
SELECT date_trunc('day', event_at) AS day, count(*) AS n
FROM events WHERE event_at >= now() - interval '8 days' GROUP BY 1)
SELECT (SELECT n FROM d ORDER BY day DESC LIMIT 1)::float
/ NULLIF(avg(n), 0) AS ratio FROM d;
-- 유일성
SELECT count(*) FROM (
SELECT order_id FROM orders GROUP BY 1 HAVING count(*) > 1) x;
-- 유효성
SELECT count(*) FROM orders WHERE amount < 0 OR status NOT IN ('NEW','PAID','CANCELLED');
しきい値は絶対値ではなく比率で
「件数が1000未満ならアラート」のようなルールは、すぐに役に立たなくなります。サービスが成長すれば、しきい値を直し続ける必要があり、誰も直しません。
その代わり、自分自身と比べます。
- 昨日の件数 / 直近7日の同じ曜日の平均 → 0.5未満なら警告
- NULLの比率が先週比で2倍以上に増加
- 鮮度の遅延が、普段のp95の3倍を超過
曜日の影響を無視すると、月曜日ごとに誤検知が出ます。週末にトラフィックが少ないサービスでは、「昨日比」は、毎週月曜日の午前に、誤ったアラートを出します。同じ曜日同士で比べてください。
データ契約
品質検査は事後対応です。根本は、上流と合意することです。
データ契約(data contract)は、生産者とコンシューマーの間の明示的な約束です。
dataset: orders
owner: order-team
schema:
order_id: { type: string, required: true, unique: true }
amount: { type: decimal, required: true, min: 0 }
status: { type: enum, values: [NEW, PAID, CANCELLED] }
created_at: { type: timestamp, required: true }
sla:
freshness: 30m # 30분 이내 데이터가 있어야 함
completeness: 0.99 # 널 비율 1% 미만
breaking_change: 30일 전 공지
契約があると、2つのことが変わります。
- 責任の所在がはっきりします。「スキーマが変わって壊れた」ではなく、「契約に違反した」になります。
- 自動検証が可能になります。契約ファイル自体が、検査ルールになります。
検査をどこに置くか
추출 → [입력 검사] → 변환 → [출력 검사] → 적재 → [사후 검사]
- 入力検査: 上流が渡したものが契約に合っているか。ここで止めれば、汚染が下流に広がりません。
- 出力検査: 自分たちの変換が作った結果が合っているか。件数の保存、合計の一致。
- 事後検査: ロードされたテーブルの鮮度・完全性。定期的に動きます。
最もよくあるミスは、事後検査だけを置くことです。そうすると、すでに汚染されたデータが、ダッシュボードや他のパイプラインに広がったあとで気づくことになります。
失敗をどう扱うか
検査が失敗したときの選択肢は、3つです。
| 対応 | いつ |
|---|---|
| 停止(fail) | 下流が誤ったデータを使ってはいけないとき。決済・精算 |
| 隔離(quarantine) | 一部だけが問題のとき。悪い行だけを別に取り出し、残りは進める |
| 警告(warn) | 品質が低くても、ないよりましなとき。探索用のデータ |
無条件の停止はいけません。些細な問題で全体が止まると、人々が検査をオフにしてしまいます。無条件の警告もいけません。誰も見ません。データセットごとに決める必要があります。
現場での姿
- 上流がファイルを送っていないのに、0件のロードで「成功」 → 鮮度・完全性の検査がない。
- スキーマに列が追加されると、パースがずれて、値が1列ずつずれる → 入力検査がない。
- 再処理後に件数が2倍 → 一意性の検査がない。
次のクイズで確認すること
品質の4つの軸をクエリで実装して、実際のデータセットに適用し、曜日を考慮した相対しきい値で異常を検知します。契約違反を作って、検査が捕まえるかを確認し、停止・隔離・警告の3つの対応を区別して適用します。