题干与适用场景
表按天写入 Parquet,单个 row group 内有许多 data page。业务查询常按 customer_id 和时间范围过滤,结果不到总行数的 1%,但扫描字节接近整张表。题目要求你说明如何利用可选的 Page Index 减少无关页读取,同时保持旧 reader 可读、控制元数据开销,并证明收益来自页跳过而不是缓存或资源变化。
题设容量、选择性和扫描比例是面试场景,不是通用基准。题目适合数据工程、湖仓引擎、查询优化和存储基础设施岗位。核心考察是列式文件布局与谓词下推,因此归为 data。
面试官考察点
第一,能否区分 ColumnIndex 与 OffsetIndex。前者用每页边界统计判断是否可能匹配,后者把命中的行范围映射到其他列的页偏移;只背名称而不说明跨列协同是不完整的。
第二,能否说明排序与无序列的差异。排序列可用边界二分定位,非排序列通常需要顺序检查每页的最小值和最大值;Page Index 不是任意数据的二级索引。
第三,能否处理正确性边界。截断的 min/max 只能扩大候选范围,不能漏掉可能匹配的页;null、NaN、列顺序和 column_orders 必须遵循格式定义。
第四,能否量化成本。索引读取和写入会增加 footer 附近的元数据,但选择性扫描可以减少数据页 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 的 page 数、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 边界不必相同,不能拿一个列的 page 编号直接索引另一个列。若文件没有 OffsetIndex,reader 可能只能顺序解码更多列,页跳过收益会下降。
第五步:评估写入和元数据成本
提高 page 数会增加 page headers 和 index 条目;过大的 page 又降低细粒度跳过能力。按典型查询的选择性、行宽、压缩率和 page 大小做矩阵测试。对高选择性点查,可接受更多索引元数据;对宽范围扫描或全表聚合,索引可能只是额外 footer I/O。
第六步:设计兼容与灰度
启用写入前检查所有消费端。旧 reader 忽略 Page Index 时,应沿用 row group statistics 或正常读取 page,结果仍正确。先对新文件和一个固定日期分区灰度,保留关闭索引的对照文件;记录支持与不支持 reader 的结果哈希、扫描字节和错误率。不要依赖某个客户端“看起来能打开”来证明端到端支持。
第七步:定义可重复验收
在冷缓存、相同并发和相同快照上运行等价查询,多次取 p50/p95。至少记录:扫描字节、读取 page 数、跳过 page 比例、footer/index 读取字节、CPU 解码时间、端到端延迟和结果校验。保留一组值域重叠高的非排序分区作为负对照;若索引只在低选择性查询上增加成本,应明确停止扩大范围。
高质量示范回答
“我会先确认使用的 reader 能否读取 ColumnIndex 和 OffsetIndex,并从 footer 抽样检查 page 数、索引长度、column_orders 和写入排序。Page Index 是可选元数据,不是二级索引;它只告诉 reader 哪些页可能匹配。
对 customer_id 已排序的 row group,我会用每页 min/max 做二分定位;对无序列则顺序检查边界。命中的谓词页再通过 OffsetIndex 映射到其他投影列的行范围,不能把一个列的页编号直接套到另一个列。截断统计只能扩大候选集合,统计缺失、null 或比较顺序不确定时安全读取,避免漏数。
写入侧用选择性、page 大小、压缩率和 footer 增长做矩阵基准。高选择性查询可接受更多索引元数据,全表聚合则未必受益。灰度时保留旧 reader 和关闭索引的对照;旧 reader 忽略索引也必须返回相同结果。最终在冷缓存和相同资源下比较扫描字节、跳过页比例、索引 I/O、CPU、p95 与结果哈希。只有读取页显著减少且结果一致,才扩大写入范围。”
常见错误
- 把 Page Index 当二级索引 → 非排序列仍可能要检查大量页 → 按值域重叠和选择性评估。
- 只写 ColumnIndex 不管 OffsetIndex → 其他投影列无法按命中行范围跳页 → 验证跨列映射。
- 把截断 min/max 当精确值 → 可能错误排除真实匹配页 → 只允许安全扩大候选集。
- 只用热缓存测收益 → 缓存掩盖真实 I/O → 冷缓存和固定对照重复运行。
- 把 page 编号跨列复用 → 各列 page 边界不同 → 使用 OffsetIndex 的行范围和偏移。
- 忽略旧 reader → 部署后出现兼容或性能回退 → 建立 reader 版本矩阵和透明回退。
- 只看延迟不校验结果 → 页跳过 bug 可能漏数 → 比较结果哈希和业务聚合。
- 默认全表启用 → 低选择性查询只增加 footer 成本 → 按分区和 workload 灰度。
追问及应对
追问一:为什么排序列更容易从 Page Index 获益?
排序让相邻页的值域更集中,给定范围通常只命中连续页,reader 可用边界二分;无序列的值域重叠更大,候选页更多。收益仍由 page 大小和谓词选择性决定。
追问二:没有 OffsetIndex 能否跳过页?
可以在谓词列上识别候选页,但投影其他列时难以安全、直接地定位同一行范围,reader 可能需要更多顺序读取。应以目标 reader 的实际实现和指标验证,不能只看文件是否含 ColumnIndex。
追问三:索引统计被截断会不会错?
正确实现应把截断边界解释为覆盖真实值域的保守边界。它可能造成假阳性、读取多余页,但不应造成假阴性。若 writer 无法保证这一点,应禁用该优化或回退正常读取。
追问四:如何证明没有漏读?
对同一快照运行启用与禁用 Page Index 的查询,比较完整结果、行数、聚合和按键抽样;再用包含边界值、null、重复值和长字符串的合成数据覆盖边界。任何差异都先关闭灰度。
追问五:何时不值得写 Page Index?
全表扫描、低选择性谓词、page 很少或 footer 读取已占主要延迟时,索引收益有限。应比较索引字节、写入 CPU 和维护成本,保留按表或分区关闭的能力。
追问六:如何处理 schema 或排序规则变化?
新文件按自己的 column_orders 和 schema 写索引,reader 按文件元数据解释,不能假设整个表共用一个排序规则。混合历史文件应按文件能力分别处理,并持续监控索引缺失比例。