Skip to content

Schema Drift 進階偵測

對應主管要求 ① 完整稽核 的最後一哩 — 從「我看到 drift」前進到「我告訴你誰造成的、跟其他環境有多大差異、外部變動我看得多快」。

Argus 既有的 Schema Drift Detection 在 PR-1 階段只能說「DB 跟基準對不上」。本頁覆蓋 #07b 三個進階能力

能力解什麼痛進入位置
drift 歸因DBA / 稽核要追責時不用人工 cross-reference audit_log + admin sessiondrift 列表每一筆自動帶歸因標籤
跨環境結構對比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_sessionDBA 走 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 分鐘

怎麼開啟

  1. 左側導覽 → 外部漂移輪詢 (/drift-events/poll-targets)
  2. 從下拉選一個資料庫(PG only — 其他引擎下方有說明)
  3. 選填一個顯示標籤(例:prod-orders-watch
  4. 加入儲存

下一個 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_actoradmin_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 Monitoringargus_drift_external_poll_ticks_total 對齊 Grafana
  • 設計:docs/system-review/design-plans/07b-schema-drift-advanced.md(內部)

Argus — 公司內部資料庫變更審計平台