題幹與適用場景
面試官給你一張持續寫入的事件表,要求建立按天彙總的事實表。第一次執行可以掃描歷史資料,但後續每次只能處理最近變動的資料;事件可能延遲、更新或重複抵達,模型欄位也可能變動。請說明篩選邊界、唯一鍵、增量策略、回補方式,以及失敗後如何復原。
本文假設倉庫支援 SQL,目標模型按 dateday 聚合,事件有 eventat、updatedat 與穩定的 eventid。這些假設應在面試開頭說清楚;若沒有穩定唯一鍵,更新語意和去重方案都會改變。
面試官考察點
面試官要看的是你能否同時做到「少掃資料」與「結果仍正確」。普通回答只會說使用 isincremental() 和時間戳篩選;高品質回答會解釋為什麼篩選視窗需要涵蓋延遲資料、為什麼彙總目標必須宣告符合粒度的 uniquekey,以及什麼時候一定要 --full-refresh。
也會觀察你是否區分三種風險:漏掉延遲抵達的紀錄、同一業務粒度重複寫入、歷史邏輯改變後新舊結果不一致。面試準備資料通常把增量模型、快照和依賴圖放在資料工程或分析工程的核心範圍;這題適合考察 SQL、資料建模與執行治理的組合能力。
回答前需要釐清的問題
業務粒度是什麼?
如果一列代表一天,dateday 可以作為唯一鍵;如果一列代表使用者和日期,鍵就應是 (userid, date_day)。粒度不同會改變合併條件、重複檢查和回補成本。
延遲資料最多晚到多久?
若事件通常只晚到兩天,可以每次重算最近三天;若沒有可接受的上限,就不能只用固定視窗,需要水位線、分區重算或定期全量校準。視窗不是越小越好,必須覆蓋業務可接受的延遲範圍。
上游更新由哪個欄位表示?
eventat 表示業務發生時間,updatedat 表示紀錄最後修改時間。只按照 eventat 篩選會漏掉「舊事件後來被修改」的情況;應優先使用可靠的 updatedat,並確認來源系統不會把它回撥。
模型邏輯或欄位變更如何發布?
新增欄位、刪除欄位與計算邏輯變更的處理方式不同。要先確認是否允許 onschemachange 自動同步、歷史欄位是否需要回填,以及發布時能否安排全量刷新。
30 秒回答框架
「我先確認目標粒度和延遲上限。第一次執行做全量建置,後續用 isincremental() 按 updatedat 篩選,並向前擴大一個延遲視窗。目標模型宣告與粒度一致的 unique_key,讓同一天的新資料更新原列而不是追加重複列。視窗內按鍵去重後再聚合,用 merge 或倉庫等價策略寫入。視窗、唯一鍵和結構變更都用測試驗證;如果邏輯改變讓歷史結果不再一致,就執行受控的 --full-refresh,並重跑下游模型。最後監控處理筆數、最大事件時間、重複鍵和新舊結果差異。」
分步驟深入解答
1. 先用全量方案定義正確性基線
先寫出全量查詢:讀取所有事件,再按目標粒度聚合。這是正確性基線,之後的增量結果必須能和相同時間範圍的全量結果對帳。沒有基線就無法判斷「少掃資料」是否造成漏算。
2. 選擇增量篩選邊界
增量模型只在目標表已存在、沒有傳入 --full-refresh,且模型設定為 incremental 時進入增量分支。可以用目標表的最大更新時間減去延遲視窗:
{{
config(
materialized = 'incremental',
unique_key = ['date_day'],
incremental_strategy = 'merge'
)
}}
with source_events as (
select *
from {{ ref('app_events') }}
{% if is_incremental() %}
where updated_at >= (
select coalesce(max(updated_at), '1900-01-01') from {{ this }}
) - interval '3 day'
{% endif %}
)
select
cast(event_at as date) as date_day,
count(distinct event_id) as events,
max(updated_at) as max_updated_at
from source_events
group by 1範例中的三天只是面試假設,不是通用常數。視窗應由延遲分布、SLA 和重算成本決定;實際日期函式也要依 adapter 調整。
3. 讓唯一鍵符合模型粒度
如果目標表按天儲存,dateday 是唯一鍵;如果按使用者和日期儲存,就使用 ['userid', 'date_day']。唯一鍵欄位不能含空值,否則 merge 可能無法匹配並產生重複列。沒有唯一鍵時,許多 adapter 只能 append,重算視窗會把同一粒度寫出多列。
4. 在視窗內先去重再聚合
同一事件可能因重放或 CDC 更新產生多筆紀錄。按 event_id 和更新時間排序,保留每個事件的最新版本,再做每日彙總:
with ranked_events as (
select
*,
row_number() over (
partition by event_id
order by updated_at desc, ingest_seq desc
) as rn
from source_events
),
deduped_events as (
select * from ranked_events where rn = 1
)
select
cast(event_at as date) as date_day,
count(*) as events,
max(updated_at) as max_updated_at
from deduped_events
group by 1只有當 ingest_seq 能穩定打破相同更新時間時才使用它;否則要把並列規則說成待確認的來源契約。去重必須在聚合前完成,否則同一事件的兩個版本會同時計數。
5. 選擇 merge、分割區覆寫或 append
merge 適合按唯一鍵更新與插入;按分割區重算的場景可以使用 insert_overwrite,它依賴分割區而非逐列唯一鍵;純追加事件且上游永不更新時,append 更簡單。選擇依據是更新語意、倉庫掃描成本和 adapter 能力,不要把一種策略當成所有倉庫的預設答案。
6. 處理結構與邏輯變更
新增欄位不一定會回填舊列;刪除欄位或型別變更也可能只在執行時暴露。onschemachange 可設定為 ignore、fail、appendnewcolumns 或 syncallcolumns,但它只追蹤頂層欄位,不能取代歷史資料回補。計算邏輯改變後,新舊歷史可能使用不同規則,此時要執行 --full-refresh,並依賴關係重建下游增量模型。
7. 設計回補和失敗復原
把延遲視窗、目標最大更新時間、來源水位寫入執行記錄。某次視窗執行失敗時,下一次仍從已提交的目標水位重新計算,不要把記憶體中的「已處理到」當成事實。大範圍歷史修復可按日期分片並限制並行;完成後用抽樣全量查詢對帳,避免一次刷新壓垮倉庫。
8. 建立驗證閉環
至少驗證四組訊號:視窗內每個 eventid 最多一列;目標唯一鍵沒有重複;最近視窗和全量重算結果的差異在允許範圍內;每次執行處理筆數和最大 updatedat 沒有異常跳變。測試要涵蓋空輸入、重複事件、舊事件更新、延遲事件、視窗邊界相等值,以及全量刷新後再增量執行。
高品質示範回答
「我會先確認模型粒度、延遲上限和來源的更新欄位。假設目標是一列一天,事件有穩定的 eventid 和 updatedat。第一次執行全量建置;後續用 is_incremental() 從目標表最大更新時間向前回看三天。這個視窗依延遲分布決定,三天不是固定答案。
視窗內先按 eventid 和更新時間去重,再按天聚合,目標模型把 dateday 設成 unique_key,用 merge 更新最近幾天,避免重複列。如果目標是使用者日粒度,就改成複合鍵。只按事件發生時間會漏掉舊事件後續更新,所以我會優先使用可靠的更新時間欄位。
我會把視窗大小、最大水位、處理筆數、重複鍵和視窗對帳差異當成執行指標。新增欄位可依結構變更策略處理,但不會自動填滿歷史值;如果計算邏輯改變或需要歷史回補,就安排分片的 full refresh,並重跑受影響的下游模型。最後用全量查詢做抽樣對帳,驗證空輸入、延遲、重複、邊界時間和失敗重試,確保增量最佳化沒有犧牲正確性。」
常見錯誤
只按 eventat 篩選 → 漏掉舊事件更新 → 使用 updatedat 或明確的 CDC 水位
事件發生時間不會隨後續修正而改變。若業務允許更新,就必須按更新時間或變更序列篩選,並確認欄位可靠。
沒有唯一鍵就使用 merge → 無法穩定匹配 → 先定義模型粒度和非空鍵
唯一鍵不是隨便選一欄;它必須唯一標識目標的一列。若粒度是使用者與日期,單獨使用日期會把不同使用者合併。
以為增量模型會自動回填新欄位 → 歷史值保持空白 → 設計回補或 full refresh
結構同步和歷史資料回填是兩件事。新增欄位只改變結構時可以輕量同步;需要舊紀錄有值時必須額外更新或重建。
固定使用一小時視窗 → 延遲分布超過視窗時漏算 → 用分位數和對帳資料校準
視窗大小應由延遲分布、SLA 和成本共同決定。監控視窗外抵達量,發現異常時擴大視窗或執行分片回補。
追問及應對
如果每天有 5% 的事件在兩天後抵達,你會如何選視窗?
先確認業務允許的正確性延遲。如果日報允許隔天修正,可以覆蓋兩到三天並把延遲事件納入對帳;如果必須在首日穩定,就需要水位線加回補佇列,不能只靠更大的 SQL 視窗。選擇應由延遲分布和成本曲線驗證,而不是直接套用比例。
如果 unique_key 在來源資料中重複,會發生什麼?
同一次 merge 的新資料或目標資料含重複鍵時,adapter 可能報錯,也可能產生不確定結果。先在增量輸入和目標表分別做唯一性檢查,找出重複來源;再按事件版本去重,或重新定義能表達真實粒度的複合鍵。不要用隨機 ID 掩蓋業務鍵不穩定。
模型 SQL 改了,但只想重算最近七天,可以繼續增量執行嗎?
只有在歷史列的計算結果不受新邏輯影響時才安全。若邏輯改變會影響全部歷史,最近七天增量會留下新舊規則混合的表,應執行受控 full refresh,或按受影響分割區分片重算,再同步重跑下游模型。
上游表被截斷後,增量模型如何復原?
先停止繼續推進水位,確認來源表重建完成,再從可靠的來源快照或 CDC 起點回補。若無法證明來源表涵蓋目標所需歷史,直接增量執行會把目標當成完整基線,必須恢復快照或執行全量重建。