題干與適用場景
這是資料平台與資料工程系統設計題。Reverse ETL 把數倉中的可信模型投遞到 CRM、行銷或產品工具;Hightouch 官方說明將流程概括為 source → model → sync → destination,Census 資料也把數倉到業務平台的同步定義為 operational analytics。題目要求你同時處理批次、增量變更、目的端 API 約束和資料治理。
面試官考察點
- 能否把模型快照、變更偵測、排程、佇列和目的端 adapter 拆開。
- 能否在來源至少一次交付下,用冪等鍵和版本保護目的端。
- 能否把刪除、同意撤回、schema 漂移和租戶隔離設計成明確契約。
- 能否用新鮮度、成功率、積壓和對帳指標證明系統可用,而非只畫資料流圖。
回答前需要釐清的問題
- 10 分鐘 SLO 只約束高優先級人群,還是所有記錄?
- 目的端是否支援批次 upsert、刪除介面、冪等鍵和服務端游標?
- 模型是否有穩定主鍵、更新時間和刪除墓碑?快照保留多久?
- 租戶之間的配額是否獨立,單一大租戶能否占滿全域吞吐?
- 同意撤回需要多快在目的端生效,失敗期間是否必須阻止新增同步?
「我把系統拆成版本化模型、變更偵測器、每租戶佇列、目的端 adapter 和對帳任務。每筆記錄帶 tenantid、穩定業務主鍵、模型版本、rowversion 和刪除狀態。偵測器用高水位或 CDC 產生至少一次任務,adapter 按目的端限流批次 upsert,並以 tenant、destination、recordid、rowversion 組成冪等鍵。重試不會降低版本;刪除和同意撤回先寫不可繞過的 fence。監控新鮮度 lag、積壓、限流、失敗分類、對帳差異和目的端刪除延遲。」
分步驟深入解答
第一步:定義來源模型與版本
把數倉模型作為同步輸入,不讓 worker 直接拼接多個業務庫。模型輸出穩定的 recordid、tenantid、業務欄位、rowversion、updatedat、consentstate 和 deletedat。每次模型執行產生 modelrunid;記錄一旦被刪除,先輸出墓碑而不是從查詢結果中靜默消失。這樣可以區分「本輪尚未掃描」與「明確要求目的端刪除」。
第二步:偵測變更與排程
優先使用模型表的更新欄位或 CDC 水位;水位應保存在持久化 checkpoint 中,並允許重疊視窗,避免相同時鐘導致漏數。一次執行寫入不可變更批次,再由排程器按租戶優先級拆成任務。高優先級佇列按 10 分鐘 SLO 計算允許延遲,低優先級任務在全域容量不足時讓路,但不能繞過同意撤回佇列。
第三步:設計冪等 upsert
任務至少一次投遞,因此「傳送成功後 worker 崩潰」必須安全重播。對目的端支援冪等鍵的介面,使用 tenantid + destination + recordid + row_version;目的端只接受不低於目前版本的寫入。若目的端沒有冪等能力,保存請求指紋和回應、限制並行,並用週期性讀取對帳;不要聲稱跨系統交易能提供 exactly-once。重試必須按可重試錯誤、指數退避和最大嘗試次數分類。
第四步:隔離限流與過載
每租戶維護令牌桶或目的端返回的剩餘配額,同時設定全域並行上限。租戶佇列、公平排程和死信佇列避免單一大租戶拖垮其他租戶。429、5xx 和網路逾時進入延遲重試;4xx schema 或權限錯誤進入人工處理佇列。佇列積壓接近 SLO 時觸發告警,並允許暫停低優先級全量回填。
第五步:處理刪除與同意撤回
撤回事件寫入獨立 deletion fence,帶 tenant、record_id 和事件版本。worker 傳送 upsert 前檢查 fence;已撤回記錄只允許傳送刪除,直到治理策略確認解除。目的端刪除成功後保留回執和時間戳,失敗則持續重試並告警。定期對帳應檢查目的端仍存在的禁止記錄,不能只統計請求成功率。
第六步:應對 schema 漂移與回滾
模型 schema 以版本發布,欄位映射在部署前做相容性檢查。新增可選欄位可灰度,型別變更或刪除欄位先產生阻斷報告,不直接讓所有租戶失敗。adapter 保留 mapping_version,失敗批次綁定舊映射重試;需要回滾時切回已驗證版本,禁止把半遷移狀態覆蓋成最新成功。
第七步:可觀測性與對帳
按租戶和目的端記錄 source_run、任務狀態、嘗試次數、最後成功版本、API 延遲、限流次數、佇列年齡和刪除延遲。核心指標包括高優先級記錄的 freshness lag p95、成功率、死信數、schema 錯誤率、目的端與來源端計數差異,以及隨機抽樣的欄位 hash 差異。每日或每次發布後跑全量對帳,自動修復可安全重播的差異,並把不可修復項交給營運。
設計取捨與邊界
快照、增量與 CDC
純快照簡單但會重複掃描;更新時間增量成本低,卻依賴穩定時鐘和更新欄位;CDC 能表達刪除,但要求來源表或建模層保留變更事實。面試中應說明選擇取決於模型刷新方式、刪除語義和目的端容量,並保留定期全量對帳作為漏數保險。
佇列位置與一致性
按租戶分區便於隔離和順序保證;全域佇列更容易利用容量,但需要公平排程。可以保證同一記錄版本單調可見,卻無法在數倉提交和目的端寫入之間提供跨系統原子提交。用版本條件寫入、重播和對帳換取可解釋的最終一致性。
全量回填與即時更新
回填應使用獨立低優先級預算、可暫停游標和限流感知;即時更新進入高優先級佇列。回填與即時任務競爭同一記錄時,以更高 row_version 勝出,並在目的端支援條件寫入時拒絕舊版本。
高品質示範回答
「我會把數倉模型當成版本化事實源,先由高水位或 CDC 生成不可變更批次,再按租戶和目的端排隊。記錄帶穩定主鍵、row_version、模型和映射版本;upsert 使用目的端冪等鍵或請求指紋,重試採用指數退避,舊版本不能覆蓋新版本。每租戶令牌桶和全域並行上限共同處理限流。刪除與同意撤回寫入 fence,worker 傳送前強制檢查,目的端保留刪除回執。透過 freshness lag、佇列年齡、死信、schema 錯誤、欄位 hash 對帳和禁止記錄殘留來驗收,明確系統提供至少一次投遞與最終一致性,而非跨系統 exactly-once。」
常見錯誤
- 把 Reverse ETL 說成即時資料庫複製,忽略模型刷新和業務欄位映射。
- 只說「訊息佇列保證 exactly-once」,沒有處理目的端重試和重複請求。
- 用全域限流代替租戶隔離,導致一個大租戶擠占全部容量。
- 從目前模型查詢中消失就當作刪除,沒有墓碑、同意撤回 fence 和目的端對帳。
- schema 變更直接廣播,失敗後無法知道哪一批使用了哪一版映射。
延伸追問
如何證明 10 分鐘新鮮度 SLO
從模型提交時間或變更事件時間開始,到目的端確認可讀結束,按高優先級記錄計算 p95 與逾時率。不要用 worker 啟動時間替代資料產生時間,也不要只看平均值。
目的端只有覆蓋式全量介面怎麼辦
為每租戶生成帶 modelrunid 的版本化快照,先上傳臨時集合並校驗計數與 hash,再原子切換版本;刪除和撤回仍需單獨 fence,不能依靠下一次全量自然消失。
如何處理目的端成功但回執遺失
重播同一冪等請求,或依據請求指紋和目的端查詢做對帳。若介面無法查詢且沒有冪等鍵,只能把不確定結果放入人工核對佇列,不能無證據地標記成功。
什麼時候需要專門的同步平台
當目的端數量、租戶配額、映射版本、治理圍欄和對帳要求超過單一 DAG 的可維護範圍時,再拆成持久化任務服務和 adapter 層。小規模單目的端可用編排器加冪等腳本起步,但仍應保留刪除與重試契約。