題幹與適用場景
一張 Apache Iceberg 事實表保存 18 個月訂單資料,Spark 批次、Flink 串流和 Trino 查詢並行使用。團隊要 把 customername 拆成 firstname、last_name,並把分割區從按日改為按月。歷史檔案不能全部重寫, 舊查詢要繼續工作,串流寫入不能停,發布失敗時必須回滾。
題目考察能否拆開 schema 相容性、欄位身份、分割區布局、snapshot 提交和讀寫器升級。Iceberg 文件說明 欄位以不重複的 ID 追蹤,schema 與 partition evolution 可獨立提交;高品質回答要把保證轉成升級步驟。
面試官考察點
重點包括區分欄位名稱與欄位身份、區分 schema 演進與歷史回填、理解新舊 partition spec 共存,以及說明 snapshot 提交、引擎相容矩陣和回滾證據。不能只說「先改表再重跑」。
回答前需要澄清的問題
- 哪些引擎版本負責讀寫,是否都支援 field ID 與目前 partition spec?
customer_name能否穩定解析,無法解析的資料如何標記?- 舊客戶端是否依賴欄位位置、
SELECT *或序列化 schema? - 查詢引擎能否混讀新舊分割區並執行謂詞下推?
- 串流 checkpoint、schema registry 和發布窗口如何協調?
- catalog 是否支援原子提交、snapshot 保留與按 ID 回滾?
30 秒回答框架
「先盤點引擎相容性和欄位使用,再用 Iceberg 欄位 ID 新增相容欄位;舊欄位暫留,解析和品質指標先旁路運行。 分割區規格獨立演進,讓新資料按月寫、舊檔案按日讀。驗證查詢、並行提交和 snapshot 後,分階段升級寫入者與 讀取者。歷史回填是獨立版本化工作,失敗時回到舊 snapshot。」
分步驟深入解答
第一步:建立相容矩陣與變更契約
記錄 schema、field ID、partition spec、snapshot 保留、寫入引擎和查詢引擎版本。把 schema 變更與 partition 變更分成兩份契約,避免解析邏輯、欄位重命名和分割區改造混在一次提交。
對 customer_name 採「新增後棄用」:增加兩個新欄位並保留舊欄位,定義單名、多語姓名、空白和解析失敗規則。 舊欄位不能靜默改成新語義,所有消費者確認遷移後才計畫刪除。
第二步:依靠 field ID 而非欄位位置
Iceberg 以穩定 ID 綁定欄位。示意變更如下:
old: id=7 customer_name:string
new: id=21 first_name:string, id=22 last_name:string
old id=7 remains until consumers migrate刪除欄位後不得把 ID 7 重分配給另一語義;巢狀 struct、map、list 的子欄位也要檢查 ID。CI 應比較變更前後 的 ID 對映,不能只看欄位名稱。
第三步:把回填與線上寫入分開
先讓批次與串流同時產生新欄位,將解析結果寫入品質指標;穩定後再逐分割區回填歷史檔案。回填讀固定 snapshot 或水位,清單記錄輸入 snapshot、程式版本、列數、失敗數與校驗和。並行提交衝突時重讀最新 snapshot 重試,不能覆蓋別人的提交。
若歷史姓名不能可靠解析,保留 null 和 nameparsestatus,不要偽造拆分結果。回填不是 schema 操作本身。
第四步:獨立演進 partition spec
新增 partition spec,讓新資料按月份轉換;舊檔案仍使用按日 spec。查詢規劃器應識別每個檔案的 spec 並裁剪。
spec-0: day(ts) -> existing files
spec-1: month(ts) -> new files先在影子表驗證掃描量、裁剪和小檔案。消費者應透過 catalog 讀 metadata,不要拼接物件儲存路徑。
第五步:安排讀寫器發布順序
先發布能讀新舊欄位的相容讀取者,再發布寫入新欄位的 producer,最後切換只依賴新欄位的消費者。流作業升級 要驗證 checkpoint 恢復與兩種 snapshot。不同引擎支援差異要在矩陣中明確列出,必要時使用視圖投影或先升級引擎。
每次只提交一個小 snapshot,記錄 writer、catalog、ID 對映與 partition spec。觀察期不要連續做刪欄位、改型別和 大規模重寫,方便定位問題。
第六步:設置驗證、提交與回滾門禁
結構層比較 ID、型別和 spec;資料層比較列數、空值率、解析失敗、金額聚合與日/月切片;行為層驗證舊 SQL、 新 SQL、checkpoint 恢復、並行衝突和分割區裁剪。保存無法解析樣本供人工複核。
發布前保存 previoussnapshotid 和 newsnapshotid。失敗時只把 catalog 指標回到舊 snapshot,保留新檔案、 清單和指標;修正後從固定輸入重做受影響分割區。
高品質示範回答
「我先盤點 Spark、Flink、Trino 版本和 field ID 支援,建立 schema、partition、消費者矩陣。新增兩個欄位並 保留 customername,用固定解析規則和 nameparse_status 量化品質;歷史回填用固定 snapshot 與版本化清單。」
「新檔案按 month(ts) 寫,舊檔案按 day(ts) 讀;先升級相容讀取者,再升級寫入者,最後遷移新欄位消費者。發布前 驗證 ID、列數、空值、聚合、查詢與 checkpoint。保存前後 snapshot,失敗時回指舊 snapshot。」
常見錯誤
- 按位置判斷相容 → 舊資料被錯誤解讀 → 核對穩定 field ID。
- 把 rename 當新增欄位 → 語義改變卻重用資料 → 保留舊欄位並明確棄用。
- 改 spec 就搬所有檔案 → 提交範圍巨大難回滾 → 新舊 spec 共存,按需重寫。
- 先升級只讀新欄位的消費者 → 舊 writer 產生空值 → 先相容讀、再升級寫、最後切換。
- 只驗證 schema 指令成功 → 查詢或流恢復仍可能失敗 → 做三層門禁。
- 回填直接寫線上 snapshot → 失敗沒有回退點 → 獨立版本化。
追問及應對
追問一:為何不能重用刪除欄位的 ID?
舊檔案仍保存原 ID,重用會讓讀取器把舊位元組當成新語義。應分配新 ID,保留舊欄位直到消費者完成遷移。
追問二:新舊分割區共存會破壞查詢嗎?
Iceberg metadata 記錄每個檔案的 spec,規劃器可分別裁剪;仍要用實際引擎驗證時間轉換、謂詞下推和小檔案。
追問三:並行提交衝突如何恢復?
讀最新 snapshot,確認輸入仍有效,重算受影響分割區後提交。不要用舊 metadata 強行覆蓋他人的 snapshot。
追問四:何時刪除舊欄位?
所有讀寫、重放、審計和匯出完成遷移,觀察期穩定且 snapshot 足夠回滾後才刪除,並先盤點隱藏消費者。
追問五:解析失敗的姓名如何處理?
寫入 null、失敗原因與受控的原始值引用,按語言和格式監控;不要把猜測結果寫入關鍵身份欄位。