Appearance
Schema Drift 進階偵測
對應主管要求 ① 完整稽核 的最後一哩 — 從「我看到 drift」前進到「我告訴你誰造成的、跟其他環境有多大差異、外部變動我看得多快」。
Argus 既有的 Schema Drift Detection 在 PR-1 階段只能說「DB 跟基準對不上」。本頁覆蓋 #07b 三個進階能力:
| 能力 | 解什麼痛 | 進入位置 |
|---|---|---|
| drift 歸因 | DBA / 稽核要追責時不用人工 cross-reference audit_log + admin session | drift 列表每一筆自動帶歸因標籤 |
| 跨環境結構對比 | release 評估要一眼看出 prod ↔ staging 結構差異 | 左側導覽 → 跨環境對比 (/drift-events/cross-env) |
| 外部來源高頻偵測 | 1 分鐘內偵測 Argus 看不見的直連 DB 變更(DBeaver / psql / 應用層遷移工具) | 左側導覽 → 外部漂移輪詢 (/drift-events/poll-targets) |
1. drift 歸因(attribution)
四種歸因標籤
每次偵測到新 drift,runner 同步跑 ±10 分鐘窗 的歸因推論,把 schema_drift_event row 標成四類之一:
| 標籤 | 判定規則 | 意義 |
|---|---|---|
plan | 窗內有 plan / task_run 的 audit 紀錄 | Argus 跑了 plan 改的,但補單流程沒走完(issue 沒 close) |
admin_execute | 窗內剛好一條 admin_execute_session | DBA 走 Argus 的 admin 終端改的;連 actor / session_id 一起記 |
external | 窗內 Argus 啥都沒看到 | 盲區 — 有人繞過 Argus 直連 DB 改 schema |
inconclusive | 多條 session 撞時間 / DB 查失敗 / 歷史回溯不出來 | 推不準,先擺一邊 |
為什麼是 ±10 分鐘
- 太小(±5 分鐘)會漏抓 — DBA 開長 session 跨 5 分鐘以上做事,前後段 drift 偵測時撞不到 session。
- 太大(±30 分鐘)會誤判 — 同 instance 另一個 DBA 同時段在做別的事,會被錯歸到無關 session。
設計 plan #07b §6 Q1 = B 鎖定 10 分鐘。改窗大小需重發 design plan。
external 自動寫 audit event
被歸因為 external 的 drift 會額外寫一條:
method: bb.drift.external_source_detected
severity: WARNING
parent: workspaces/{workspace}
resource: instances/{instance}/databases/{db}走既有 audit_log webhook fan-out — SIEM / 監控 / 通知 channel 都能訂閱這個 method 過濾「未授權變更」訊號。月度合規報告會撈到「本月外部來源 drift 數」指標。
2. 跨環境結構對比
進入點
/drift-events/cross-env — 兩個資料庫下拉 + 一個「比對」按鈕。
純讀路徑 — 後端 不寫 drift 事件、不發 audit、不影響任何 runner 狀態。只是把兩個 db_schema 同步結果做即時 diff。
看得到什麼
結果分三 bucket:
| Bucket | 內容 |
|---|---|
| 僅 A 有 | A 有 table,B 沒有 |
| 僅 B 有 | B 有 table,A 沒有 |
| 兩側皆有但結構不同 | 列出 column 層級差異:B 新增 columns / B 缺少 columns / column 型別或可空性變更 |
兩側皆有但細節列表全空 → 渲染「非欄位層級差異(索引 / 外鍵 / 約束)」標籤;避免讓 auditor 誤以為「沒差」。
為什麼粒度只到 column
設計 plan #07b §6 Q3 = B — table + column 粒度跟 drift checker 同步,查詢成本可控。Index / FK / constraint 層級的對比留待 Q3=C 後續 PR。
典型使用情境
- Release 評估:staging 跑過的 schema,prod 要 release 前先比一次。
- 多 region prod 一致性檢查:region-A 與 region-B 是否真的同步。
- Dev / 測試環境漂移盤點:cloned dev DB 跟 parent 偏離多遠。
3. 外部來源高頻偵測(opt-in)
為什麼要有
既有 drift runner 每 10 分鐘 tick 一次。對 external 歸因的 drift(外部直連 DB),最壞情況要等 10 分鐘才偵測到。
opt-in 高頻 PG catalog 輪詢把這個延遲壓到 1 分鐘。
怎麼開啟
- 左側導覽 → 外部漂移輪詢 (
/drift-events/poll-targets) - 從下拉選一個資料庫(PG only — 其他引擎下方有說明)
- 選填一個顯示標籤(例:
prod-orders-watch) - 點 加入 → 儲存
下一個 runner tick(最多 1 分鐘後)會把這目標納入監聽。
怎麼運作
每 tick(預設 60 秒,可設 60–600)對每個 opt-in target:
1. 對 DB 執行一條「catalog hash 查詢」
— hash 來自 pg_class + pg_attribute,單回查、毫秒級
2. 跟上次 tick 的 hash 比對:
- 第一次觀測 → 記住 hash,不動作
- hash 同 → 不動作
- hash 不同 → 強制 sync + drift check
3. drift check 觸發既有歸因流程
(本頁 §1 那條鏈),external 歸因會自動寫 audit event關鍵:catalog hash 查詢 不是完整 schema introspection — 它只算 pg_class 列表 + pg_attribute 列數的 MD5;hash 變了才花 sync + check 的成本。
為什麼只支援 PostgreSQL
設計 plan #07b §6 Q2 = B 鎖定純 polling 路線(不做 PG trigger),catalog 查詢語法 PG-specific。MySQL / MSSQL 走 information_schema 的對應版本是 Phase 2 工作。
對非 PG instance 的目標:runner 不會崩,但 log 出
engine_unsupported並計入對應 Prometheus 標籤,跳過。
Tick 間隔的選擇
- 60 秒(預設):catch latency 最短,DB catalog 負載可忽略。
- 120–300 秒:對 DB 連線資源緊張的環境可放寬。
- 超過 600 秒:服務端鉗到 600 — 比 10 分鐘 drift runner 慢就失去「opt-in 快通」的價值,要慢請直接停用而非調寬。
UI 輸入超出 60–600 範圍會即時警告字提示「儲存時會被服務端鉗制」。
暫停不刪
每個 target 帶一個 enabled 開關。先停掉觀察一段時間 → 確認真的不要 → 再移除,避免操作失誤一鍵清空清單。
4. 操作觀察點
從 audit_log 過濾
method = bb.drift.external_source_detected只取「外部來源 drift」訊號 — 給 SIEM / 通知 channel 訂閱用。
從 Prometheus 過濾
| 指標 | 含義 |
|---|---|
argus_drift_external_poll_ticks_total{outcome="change_triggered"} | 高頻輪詢實際觸發了多少次 drift check |
argus_drift_external_poll_ticks_total{outcome="engine_unsupported"} | opt-in 列表內的非 PG 目標被跳過幾次 |
argus_drift_external_poll_ticks_total{outcome="no_change"} | tick 比率正常但 catalog 沒變 — runner 在跑 |
argus_drift_external_poll_ticks_total{outcome="sync_error|check_error"} | 變化偵測到但 sync 或 drift check 失敗,需排查 |
argus_drift_external_poll_ticks_total{outcome="no_targets"} 為主:表示 setting 是空的,所有 tick 都直接 return。
從 schema_drift_event 過濾
新加的三欄:
sql
SELECT instance, db_name, detected_at, attribution, attribution_actor
FROM schema_drift_event
WHERE workspace = $1
AND attribution = 'external'
AND detected_at >= now() - interval '7 days'
ORDER BY detected_at DESC;attribution_actor 在 admin_execute 歸因時才有值(就是觸發 session 的人)。
5. 已知限制(後續 PR 處理)
| 限制 | 跡證 | 下個 PR |
|---|---|---|
| 外部偵測只支援 PG | 非 PG 目標被 engine_unsupported 跳過 | Phase 2 — 補 MySQL / MSSQL catalog hash 語法 |
| 歸因規則固定(沒 ML) | inconclusive 標籤偶爾出現 | 真的累積很多 false positive 才開新題;現規則覆 80% |
| 沒「external drift 自動暫停」整合 | Q5=C 路線明確不採 | 不做(過度反應風險) |
| 跨環境對比沒 index / FK / constraint 粒度 | altered 但 column 空 → 顯示「非欄位層級差異」 | Q3=C 後續,查詢成本評估後決定 |
6. 相關
- 概念:Schema Drift Detection — 第一道偵測 + 通用補單流程
- 操作:Audit Log — 訂閱
bb.drift.external_source_detected - 操作:Release Monitoring —
argus_drift_external_poll_ticks_total對齊 Grafana - 設計:
docs/system-review/design-plans/07b-schema-drift-advanced.md(內部)