題幹與適用場景
一張訂單湖表已被批次、串流和臨時分析共同讀取。業務要新增巢狀欄位、重新命名欄位,並把按月分區逐步改成按日分區;舊任務不能同時升級,也不想完整重寫歷史檔案。你要說明 Iceberg 如何儲存演進,以及新舊布局共存時如何維持正確性。
預設表由 Iceberg Catalog 管理,讀寫引擎支援目標版本;題目重點是表格式的 Schema 與分區演進,不是特定 Spark SQL 語法。
面試官考察什麼
- 能否區分欄位 ID、Schema ID、Partition Spec ID 和快照。
- 能否解釋為何新增、刪除、重新命名和型別擴展不必重寫舊檔案,以及仍有哪些限制。
- 能否說明新舊分區如何共存、查詢如何規劃多個分區規範,何時需要重寫資料。
- 能否提出遷移前後的相容性、效能、並行提交和回滾驗證。
回答前要釐清的問題
- 讀者使用 Iceberg 原生表格式,還是只把目錄當作 Hive 表?後者不會自動取得同樣的欄位 ID 語意。
- 要演進的是頂層欄位、巢狀欄位,還是分區轉換?巢狀結構和分區欄位有額外限制。
- 舊讀者是否按位置讀取欄位或快取舊 Schema?必須確認引擎遵守 Iceberg 的欄位 ID 對應。
- 目標是減少掃描、修復熱點,還是只改變邏輯 Schema?分區調整的收益要用查詢指標驗證。
30 秒回答框架
「Iceberg 把表狀態放在版本化元資料中,用不重複的欄位 ID 對應欄位,而不是依賴位置或重用名稱。Schema 變更產生新的 Schema ID,分區變更產生新的 Partition Spec ID;舊資料檔案保留原布局,新寫入使用新規範,查詢會針對多個規範分別規劃並利用隱藏分區裁剪。我會先做相容性檢查,再以元資料提交原子發布變更,最後用新舊讀者、重新命名正確性、掃描檔案數、失敗重試和快照回滾驗證。」
分步深入分析
1. 用欄位 ID 維持欄位身分
Iceberg 為每個欄位分配表內不重複的 ID。重新命名只改名稱,讀取仍按 ID 找到原欄位;刪除後再使用同名欄位會得到新 ID,避免舊檔案的值被錯誤復活。按位置或重用名稱都可能造成錯配。
舊 Schema: id=17, name="customer_id"
新 Schema: id=17, name="account_id"
新增欄位: id=42, name="region"2. 區分 Schema ID 與欄位 ID
欄位 ID回答「這個欄位是誰」;Schema ID回答「這一版表結構是什麼」。一次演進會產生新的 Schema 物件並設為目前 Schema ID,快照記錄寫入時使用的 Schema ID。讀者不能只快取欄位名稱陣列。
3. 判斷哪些 Schema 變更安全
新增、刪除、重新命名、重排和部分型別擴大是 Iceberg 支援的演進,但不表示所有型別變更都安全。要檢查格式版本、分區轉換和取值範圍;參與 bucket 轉換的欄位若改變轉換結果,會破壞分區語意。Map key 的結構變化也有限制。
安全候選:增加可選欄位、重新命名非分區欄位、int -> long(轉換結果不變)
需要阻擋:縮窄型別、改變 bucket 輸入語意、刪除仍被關鍵讀者依賴的欄位4. 讓新舊分區規範共存
分區演進會建立新的 Partition Spec ID;舊檔案仍使用舊規範,新檔案使用預設的新規範。讀取時要按每個規範解釋檔案,再合併結果。隱藏分區讓查詢按資料值表達過濾條件,不必把某個日期目錄寫死在 SQL。
5. 評估「不需重寫」與查詢效能
元資料變更不重寫資料檔案,降低遷移成本,但舊檔案仍按舊布局保存。多規範共存可能讓查詢產生多組 split,裁剪效果也不同。比較掃描檔案數、規劃時間、讀取位元組、任務傾斜和小檔案數;舊布局若持續成為熱點,再用受控重寫改善。
6. 用原子提交、快照和回滾保護發布
表狀態由元資料檔案和快照組成,更新透過原子替換目前元資料指標提交。發布前讀取目前版本,基於該版本建立新元資料並提交;衝突時重新讀取再重試。把變更與快照 ID 綁定,讀寫錯誤時可以切回已驗證快照。
高品質示範回答
我會把需求拆成欄位身分、結構版本和物理布局三層。欄位 ID 保證重新命名和重排不會把舊檔案的值映射錯;Schema ID 記錄這一版結構;Partition Spec ID 記錄這一版分區轉換。新增 region、重新命名 customer_id 等元資料操作不需要重寫舊檔案,但要先確認所有引擎按欄位 ID 讀取。
分區從月改日時,我會建立新規範讓新寫入使用它,保留舊檔案規範。查詢規劃器要對每個規範使用對應分區表達式,再合併 split。發布前做新舊讀者讀寫矩陣,抽樣比較重新命名前後的值,測掃描檔案數和讀取位元組,並在隔離分支提交變更。若衝突或指標惡化,按快照回滾;舊布局若長期拖慢熱點查詢,再安排有預算的重寫。
常見錯誤與改進
- 錯誤表現 → 認為改欄名等同於改檔案欄位順序 → 失敗原因 → 位置綁定會錯配值 → 修正方法 → 說明欄位 ID 的穩定身分並驗證引擎使用它。
- 錯誤表現 → 演進分區後只掃描新目錄 → 失敗原因 → 舊檔案仍屬於表,結果會漏資料 → 修正方法 → 保留並解釋每個 Partition Spec,使用規範感知的規劃。
- 錯誤表現 → 「不重寫檔案」就代表沒有效能成本 → 失敗原因 → 多規範 split 和舊布局仍可能增加掃描 → 修正方法 → 測規劃時間、檔案數、位元組和傾斜,必要時分批重寫。
- 錯誤表現 → 直接覆蓋目前元資料 → 失敗原因 → 並行提交或失敗重試可能遺失更新 → 修正方法 → 基於讀取版本原子提交,衝突時重新讀取並保存回滾點。
追問及應對
為什麼不能刪除欄位後再使用同名欄位?
可以重新新增,但必須取得新的欄位 ID。若重用舊 ID,舊檔案原欄位的值可能被誤讀成新欄位,破壞刪除語意。驗證要檢查舊檔案、新檔案和同名重建後的 ID。
把 int 提升成 long 一定安全嗎?
不一定。要檢查格式版本、取值範圍、下游型別及欄位是否參與分區轉換。若 bucket 等轉換的輸入結果改變,舊新檔案的分區語意可能不一致;應阻擋變更或先設計新規範。
舊檔案按月、新檔案按日,查詢會不會漏讀?
正確實作不會因規範不同而漏讀。查詢應針對每個 Partition Spec 解釋檔案分區值並分別裁剪,再合併結果。測試要包含跨越演進時間點的範圍查詢,並與全表掃描的資料列集合比對。
什麼時候仍需要重寫資料檔案?
當舊布局造成持續掃描、傾斜、小檔案或儲存成本時,才用受控重寫改善物理狀態;Schema 或分區元資料演進本身不要求重寫。重寫要有快照、並行提交和預算,避免變成不可回滾的大遷移。