題幹與適用場景
表格按日寫入 Parquet,單個 row group 內有許多 data page。業務查詢常按 customer_id 與時間範圍過濾,結果不到總資料列的 1%,但掃描位元組接近整張表。題目要求說明如何利用可選的 Page Index 減少無關頁面讀取,同時保持舊 reader 可讀、控制中繼資料成本,並證明收益來自頁面跳過而非快取或資源變更。
題設容量、選擇性與掃描比例是面試場景,不是通用基準。題目適合資料工程、湖倉引擎、查詢最佳化與儲存基礎設施職位。核心能力是欄式檔案布局與謂詞下推,因此歸為 data。
面試官考察點
第一,能否區分 ColumnIndex 與 OffsetIndex。前者用每頁邊界統計判斷是否可能命中,後者把命中資料列範圍映射到其他欄位的頁面偏移;只背名稱而不說明跨欄位協同並不完整。
第二,能否說明排序與無序欄位的差異。排序欄位可用邊界二分定位,非排序欄位通常要順序檢查每頁最小值與最大值;Page Index 不是任意資料的二級索引。
第三,能否處理正確性邊界。截斷的 min/max 只能擴大候選範圍,不能排除可能命中的頁;null、NaN、欄位順序與 column_orders 必須遵循格式定義。
第四,能否量化成本。索引讀取與寫入會增加 footer 附近的中繼資料,但選擇性掃描可減少 data page I/O;應按實際查詢選擇性做基準,不承諾固定倍數收益。
第五,能否設計相容回退。舊 reader 可能忽略 Page Index,檔案仍應依靠普通 row group/page statistics 正確讀取;啟用索引不能改變結果語義。
回答前需要釐清的問題
- 查詢引擎與 reader 是否實作 ColumnIndex、OffsetIndex,是否預設讀取?
customer_id寫入時是否按範圍或排序聚簇,還是完全無序?- 過濾謂詞是等值、範圍、前綴還是複雜函式?
- 現有檔案是否寫入 page-level statistics,壓縮編碼與 page 大小是多少?
- 舊 reader 的最低版本與跨語言讀取矩陣為何?
- 要最佳化點查、範圍掃描,還是全表聚合?後者通常無法從頁面跳過獲益。
30 秒回答框架
「我先確認 reader 支援 Page Index,抽樣讀取檔案 footer,統計每個 row group 的頁數、ColumnIndex 大小、排序欄位與謂詞選擇性。對已排序的 customer_id,用每頁 min/max 定位候選頁;對其他欄位,檢查邊界後用 OffsetIndex 將命中資料列範圍映射到投影欄位。寫入端只在收益明確時調整排序與 page 大小,避免把 Page Index 當二級索引。舊 reader 忽略索引也應回傳相同結果。最後用冷快取、相同資源及未啟用索引的對照比較掃描位元組、讀取頁數、規劃時間、端到端 p95 與 footer 成本。」
分步驟深入解答
第一步:確認格式與 reader 能力
Page Index 是 ColumnChunk 的可選中繼資料,包含 ColumnIndex 與 OffsetIndex。先讀取檔案中繼資料,確認索引位置、長度、欄位順序與 column_orders,並在目標引擎開啟明確的 page pruning 指標。若引擎只支援寫入、不支援讀取,寫索引不會自動減少掃描。
for each row_group:
read ColumnIndex for predicate columns
select pages whose min/max may match predicate
use OffsetIndex to map selected row ranges to projected columns
read only those page ranges第二步:區分排序與無序欄位
Parquet 文件指出,排序欄位的頁面邊界可用於二分搜尋;無序欄位通常要順序檢查每頁 min/max。排序不是格式強制前提,Page Index 仍可提供裁剪,但收益取決於相鄰頁的值域重疊程度。應記錄每個 row group 的值域重疊率,而不是只看全表基數。
第三步:安全解讀 min/max
有些 writer 會截斷長字串或使用能涵蓋真實值域的邊界。截斷邊界可能讓 reader 讀取更多候選頁,但不能排除真實可能命中的頁。null、NaN 與比較順序必須按 column_orders 解讀;統計不完整時,安全回退是讀取該頁。
第四步:串起跨欄位讀取
ColumnIndex 只定位謂詞欄位的候選頁。查詢還要投影其他欄位,因此需要 OffsetIndex 依命中資料列範圍跳到其他欄位對應頁面。不同欄位的 page 邊界不必相同,不能拿一個欄位的頁面編號直接索引另一欄位。若沒有 OffsetIndex,reader 可能要順序解碼更多欄位。
第五步:評估寫入與中繼資料成本
提高 page 數會增加 page headers 與索引條目;過大的 page 又降低細粒度跳過能力。按典型查詢選擇性、資料列寬度、壓縮率與 page 大小做矩陣測試。高選擇性點查可接受更多索引中繼資料;範圍掃描或全表聚合可能只增加 footer I/O。
第六步:設計相容與灰度
啟用寫入前檢查所有消費端。舊 reader 忽略 Page Index 時,應沿用 row group statistics 或正常讀取 page,結果仍正確。先對新檔案與固定日期分割區灰度,保留關閉索引的對照檔案;記錄支援與不支援 reader 的結果雜湊、掃描位元組與錯誤率。
第七步:定義可重複驗收
在冷快取、相同並行與相同 snapshot 上執行等價查詢,多次取 p50/p95。至少記錄掃描位元組、讀取頁數、跳過頁比例、footer/index 讀取位元組、CPU 解碼時間、端到端延遲與結果校驗。保留值域重疊高的非排序分割區作負對照。
高品質示範回答
「我會先確認 reader 能否讀取 ColumnIndex 與 OffsetIndex,從 footer 抽樣檢查 page 數、索引長度、column_orders 與寫入排序。Page Index 是可選中繼資料,不是二級索引;它只告訴 reader 哪些頁可能命中。
對已排序的 customer_id,用每頁 min/max 二分定位;無序欄位則順序檢查邊界。命中的謂詞頁再透過 OffsetIndex 映射到其他投影欄位的資料列範圍,不能把一欄的頁面編號套到另一欄。截斷統計只能擴大候選集合,統計缺失、null 或比較順序不確定時安全讀取。
寫入端以選擇性、page 大小、壓縮率與 footer 增長做基準。灰度時保留舊 reader 與關閉索引的對照;舊 reader 忽略索引也必須回傳相同結果。最後在冷快取與相同資源下比較掃描位元組、跳過頁比例、索引 I/O、CPU、p95 與結果雜湊,只有一致且讀取頁顯著減少才擴大。」
常見錯誤
- 把 Page Index 當二級索引 → 非排序欄位仍可能要檢查大量頁 → 按值域重疊與選擇性評估。
- 只寫 ColumnIndex 不管 OffsetIndex → 其他投影欄位無法按命中資料列範圍跳頁 → 驗證跨欄位映射。
- 把截斷 min/max 當精確值 → 可能錯誤排除真實命中頁 → 只允許安全擴大候選集。
- 只用熱快取測收益 → 快取掩蓋真實 I/O → 冷快取與固定對照重複執行。
- 把頁面編號跨欄位複用 → 各欄位 page 邊界不同 → 使用 OffsetIndex 的資料列範圍與偏移。
- 忽略舊 reader → 部署後出現相容或效能回退 → 建立 reader 版本矩陣。
- 只看延遲不校驗結果 → 跳頁 bug 可能漏資料 → 比較結果雜湊與業務聚合。
- 預設全表啟用 → 低選擇性查詢只增加 footer 成本 → 按表或分割區灰度。
追問及應對
追問一:為什麼排序欄位更容易受益?
排序讓相鄰頁的值域更集中,範圍查詢通常只命中連續頁,reader 可用邊界二分;無序欄位的值域重疊較大,候選頁更多。收益仍由 page 大小與謂詞選擇性決定。
追問二:沒有 OffsetIndex 能否跳過頁?
可以在謂詞欄位識別候選頁,但投影其他欄位時難以直接定位同一資料列範圍,reader 可能需要更多順序讀取。應以目標 reader 的實作與指標驗證。
追問三:索引統計被截斷會不會錯?
正確實作應把截斷邊界解讀為涵蓋真實值域的保守邊界。它可能造成假陽性、讀取多餘頁,但不應造成假陰性。否則應回退正常讀取。
追問四:如何證明沒有漏讀?
對同一 snapshot 執行啟用與停用 Page Index 的查詢,比較完整結果、資料列數、聚合與按鍵抽樣;再用包含邊界值、null、重複值與長字串的合成資料覆蓋邊界。
追問五:何時不值得寫 Page Index?
全表掃描、低選擇性謂詞、page 很少或 footer 讀取已佔主要延遲時,索引收益有限。應比較索引位元組、寫入 CPU 與維護成本,保留按表或分割區關閉能力。
追問六:如何處理 schema 或排序規則變化?
新檔案按自己的 column_orders 與 schema 寫索引,reader 按檔案中繼資料解讀,不能假設整張表共用一個排序規則。混合歷史檔案應按檔案能力分別處理。