数据工程面试:如何验证 DataFusion 嵌套字段下推真的减少了扫描成本?
题目
DataFusion 53 可以把 get_field 这样的表达式下推到数据源。你如何设计查询、执行计划和基准,证明它减少了 I/O 与解码成本且不改变结果?
场景与适用边界
假设 Parquet 表含有宽大的 struct 列 s,查询只需要 s['label'] 并用 s['value'] 过滤。回答要覆盖批量扫描、统计信息、Null、Schema 演进和下推不生效时的回退;不要把“计划里出现 projection”直接当成端到端收益。
面试官考察点
考察你能否把优化拆成语义正确性、计划改写、数据源能力和可观测收益四层。DataFusion 53 将嵌套字段访问推近扫描节点,避免读取整个 struct;配置文档说明 enableleafexpressionpushdown 会把 getfield 从过滤、排序或连接表达式提取到更靠近叶节点的位置。
回答前可以先确认:
- 数据源是否支持字段级投影,文件格式是否为 Parquet?
s['label']和s['value']的类型、Null 语义及缺失字段如何定义?- 基线是关闭优化、旧版本 DataFusion,还是读取整个 struct 的手写查询?
- 需要比较扫描字节、解码 CPU、峰值内存、延迟,还是云存储请求数?
30 秒回答框架
先给出带嵌套字段投影和过滤的 SQL;再说明如何对比优化前后逻辑计划、物理计划和 Parquet reader 的投影;最后用相同数据、缓存状态和并发度测量扫描字节、解码时间、峰值内存及结果校验,并说明何时回退。
分步骤深入解答
- 构造数据:生成相同分区、行组和统计信息的 Parquet 文件,控制 struct 宽度、Null 比例和字段选择性。
- 定义基线:固定 DataFusion 版本、线程数、对象存储延迟和缓存状态,分别运行关闭下推、读取完整 struct、只读叶字段三组查询。
- 验证计划:确认
get_field位于扫描附近,投影只包含id、s.label和过滤所需的s.value;不能只看 SQL 文本。 - 观察数据源:记录 Parquet 读取列、行组裁剪、读取字节和解码批次,区分字段投影收益与谓词下推收益。
- 校验结果:对结果做排序后的哈希或逐行比较,覆盖缺失字段、Null、类型变化、空 struct 和重复行。
- 设定回退:数据源不支持字段下推、计划无法安全改写或收益低于阈值时,保留正确的完整读取路径并记录原因。
高质量示范回答
我会先用固定的 Parquet 数据集建立三组基线:读取完整 s、关闭 enableleafexpression_pushdown,以及开启优化并查询叶字段。查询如下:
SELECT id, s['label']
FROM events
WHERE s['value'] > 150;然后保存逻辑和物理计划,确认 get_field 被推到扫描节点附近,扫描投影不再包含整个 s。基准在冷缓存和热缓存各跑多轮,记录读取字节、对象存储请求、Parquet 解码 CPU、峰值内存、端到端延迟和输出行数。结果用稳定排序后的哈希与完整 struct 基线比较,专门覆盖 Null、缺失字段和新旧 Schema。若数据源不支持字段级投影,我会保留完整读取并发出指标,而不是为了计划好看牺牲正确性。上线前把扫描字节和结果哈希加入回归门槛。
常见错误
- 只比较端到端延迟,没有控制缓存、并发和文件布局。
- 把字段投影、谓词下推和行组裁剪混成一个收益数字。
- 只验证正常值,忽略 Null、缺失字段和 Schema 演进。
- 看到计划改写就宣布优化成功,没有检查 reader 实际读取的列和字节。
- 下推失败时强行改写结果,缺少正确性优先的回退路径。
评估时看能否连接 SQL、计划、数据源和指标四层,给出可复现基线与结果校验,明确收益归因和回退条件。一般回答只说“投影下推会更快”,没有实验控制或正确性证据。
追问及应对
为什么读取叶字段可能仍然没有收益?
文件可能按行存储、struct 未被物理拆分、对象存储请求成为瓶颈,或数据源没有实现字段级投影。要看实际读取字节和解码时间,不能只看计划。
如果 s['value'] 大多为 Null,如何避免改变结果?
先固定 SQL 的 Null 语义和类型规则,用完整读取基线逐行比较;优化只能减少不需要的字段读取,不能把 Null 当成缺失或错误值。
Schema 新增嵌套字段后,旧文件怎么办?
读取器应按字段名和默认值解析旧文件,缺失字段保持定义的 Null 或默认语义;基准必须同时包含旧、新文件,并验证合并扫描结果。
面试作答要点
一句话总结
证明下推优化要同时验证计划位置、实际扫描、结果一致性和不可用时的安全回退。