具代表性的面試主題

資料工程面試:Iceberg v3 的 Variant 何時優於 JSON 字串?

資料困難
Offer.cc 編輯團隊發佈 更新

題幹

事件湖需要容納不斷變化的供應商 payload。請比較 Iceberg v3 Variant、JSON 字串和固定 struct,說明如何治理 schema、避免查詢退化,並處理舊引擎讀取與回填。

題幹與適用場景

一個事件湖接收多個供應商的 webhook。核心欄位穩定,但擴充欄位變化頻繁,且部分事件包含日期、時間戳、二進位和小數。請設計 Iceberg v3 的儲存模型,比較 Variant、JSON 字串和 struct 的邊界,並給出遷移和驗收計畫。

題目考察半結構化資料建模,而不是把所有資料都塞進 Variant。Iceberg 規範把 Variant 定義為跨列結構和型別都可能變化的值,支援比 JSON 更豐富的 primitive;它是 v3 能力,必須納入 format-version 和讀者相容矩陣。

面試官考察點

強回答會把穩定、高頻過濾欄位提升為頂層欄位,把低頻或變化快的擴充保留為 Variant,並說明 Variant 陣列和物件不等同於固定型別 list、struct。它會討論統計資訊、謂詞下推、投影成本和跨引擎支援,而不是只說「靈活」。

面試官也看重資料契約、欄位命名、型別衝突、隱私治理、回填和降級路徑。優秀回答會給出從原始 payload 到規範化欄位的雙寫或視圖策略,並明確舊引擎不能讀取 v3 特性時的處理方式。

回答前需要釐清的問題

哪些欄位是業務主鍵和過濾鍵

確認租戶 ID、事件類型、發生時間和冪等鍵是否穩定且高頻查詢。它們應成為型別明確的欄位,不能依賴每次從 Variant 解析。

擴充欄位需要怎樣的查詢保證

若只是稽核回放,Variant 可保留原貌;若要低延遲聚合或分割區裁剪,應把經過驗證的路徑提升為欄位或物化視圖。

讀取引擎是否全部支援 v3

確認 Spark、Flink、Trino、伺服器 SDK 和匯出任務的版本。若有 v2 讀者,必須定義降級為 JSON、隔離 v3 表或延遲升級的策略。

30 秒回答框架

「我先把穩定且高頻過濾的欄位做成頂層欄位,變化快的低頻擴充才放 Variant。Variant 比 JSON 字串保留更多型別,但不會自動帶來欄式統計和謂詞下推;我會透過路徑白名單、資料品質規則和物化欄位控制查詢成本。表升級到 v3 前先盤點所有讀者,舊引擎走相容視圖或延遲讀取,最後用查詢延遲、掃描位元組、型別衝突和回填成功率驗收。」

分步驟深入解答

第一步:劃分規範欄位和擴充域

將租戶 ID、事件名、事件時間、來源和冪等鍵放在頂層 struct 欄位,統一型別、可選性和欄位 ID。供應商特有的低頻物件放入 Variant,並保留來源版本和原始事件 ID,便於重播和追責。

第二步:比較三種表示

固定 struct 適合穩定 schema、強型別計算和欄級統計;JSON 字串相容性高但需要重複解析,日期、小數和二進位語義容易遺失;Variant 允許跨列變化的物件、陣列和更豐富 primitive,但引擎支援、統計和治理成本更高。

第三步:定義 Variant 契約

為允許的路徑建立 registry:路徑、期望型別、敏感級別、負責團隊、首次出現版本和是否可提升為欄位。寫入時拒絕未知高風險型別,或將其隔離到 quarantine,而不是靜默轉成字串。

text
event_id: string
event_time: timestamptz
payload: variant
payload_registry:
  vendor.order.total: decimal(18,2)
  vendor.order.shipped_at: timestamptz

註冊表是治理依據,不是把所有 Variant 路徑硬編碼進表 schema;一旦某路徑成為核心查詢,才透過 schema evolution 或物化欄位正式提升。

第四步:控制查詢成本

避免在大掃描中對 Variant 做無界萬用字元遍歷。為穩定路徑建立投影視圖或物化欄位,按事件類型和時間分割區,記錄掃描位元組、解析 CPU 和命中率。對未知路徑採樣和離線 profiling,確認收益後再提升。

第五步:處理版本與跨引擎讀取

Iceberg v3 才允許 Variant。發布前檢查每個 reader 的 format-version、Parquet/Avro 映射和 SDK 支援;不支援 v3 的任務可讀相容視圖,其中 Variant 被序列化為 JSON,但必須標明型別精度損失和不可查詢欄位。

第六步:設計回填、衝突和隱私策略

新增規範欄位時從 Variant 回填,保留原始 payload 和轉換版本。遇到同一路徑出現 string 與 decimal 衝突,不要靜默覆蓋:按版本拆路徑、記錄衝突指標,必要時把壞資料隔離。Variant 也要執行欄位級脫敏、刪除請求和存取稽核。

第七步:建立驗收矩陣

測試空值、混合型別、深層陣列、時區、精度、未知欄位、舊 reader、並發寫入和重試。指標包括查詢掃描位元組、解析 CPU、p95 延遲、型別衝突率、回填重播一致性和 v2/v3 reader 成功率。

高品質示範回答

我不會把 webhook 全部存成 JSON 字串。租戶、事件類型、時間和冪等鍵進入頂層強型別欄位;變化快的供應商擴充放進 Variant,並以 registry 管理路徑、型別和敏感級別。Variant 保留日期、時間戳、小數等型別,比字串少一次解析,但不能假設所有引擎都能做高效下推。

表升級到 Iceberg v3 前,我盤點所有 reader,給 v2 任務提供 JSON 相容視圖並記錄精度損失。查詢側將高頻路徑物化,未知路徑採樣 profiling;型別衝突進入隔離流,回填保留版本和原始事件。最後用掃描位元組、p95、衝突率、回填一致性和跨引擎成功率驗收。

常見錯誤

  • 錯誤表現 → 所有欄位都放 Variant → 失敗原因 → 核心過濾失去型別和統計,查詢成本不可控 → 修正方法 → 穩定欄位頂層化,Variant 只承載變化擴充。
  • 錯誤表現 → 把 Variant 當成 JSON 字串 → 失敗原因 → 遺失日期、小數、二進位等型別語義 → 修正方法 → 依 registry 保留真實 primitive 型別。
  • 錯誤表現 → 未驗證 reader 就升級 v3 → 失敗原因 → 舊引擎可能無法讀取表或靜默降級 → 修正方法 → 建立版本矩陣和相容視圖。
  • 錯誤表現 → 型別衝突時強制轉 string → 失敗原因 → 下游聚合和約束被破壞 → 修正方法 → 按版本拆路徑或隔離壞資料並計量。
  • 錯誤表現 → 回填直接覆蓋原始 payload → 失敗原因 → 無法重播和稽核轉換差異 → 修正方法 → 保留原始事件、轉換版本和冪等回填作業。

追問及應對

追問一:為什麼不直接使用 JSON 字串,等查詢時再解析?

字串最相容,但每次查詢都要解析,型別和精度也依賴解析器。若欄位只稽核不查詢可以接受;一旦需要聚合、過濾或跨引擎一致性,Variant 或規範欄位能把型別契約前移。

追問二:同一路徑今天是數字,明天變成字串,怎麼辦?

先按 registry 拒絕或隔離不符合型別的寫入,記錄供應商版本。若業務確實允許兩種型別,拆成帶版本的路徑或顯式 union 約定,不能讓查詢引擎自行猜測。

追問三:舊引擎只能讀取 Iceberg v2,如何漸進遷移?

保留 v2 相容表或視圖,將 Variant 序列化為 JSON 並標註精度損失;新 reader 先影子讀取 v3,再按任務灰度切換。所有任務在升級門禁中宣告最小 format-version。

追問四:什麼時候把 Variant 路徑提升為頂層欄位?

當路徑穩定、存取頻率高、型別衝突率低且 profiling 證明能減少掃描或解析成本時提升。遷移後仍保留一段時間的原始 Variant,以便核對和回放,確認穩定後再評估清理。

公開來源

同類題目