Polars scan_parquet启用Hive分区后仍全量扫描数据集问题咨询
我使用pl.sink_parquet创建了一个按date和part_id分区的Parquet数据集,结构如下:
-dataset | -date=2009-08-01 | -part_id=0 | -0.parquet -part_id=1 | -0.parquet -... -part_id=N | -0.parquet -date=2009-09-01 | -part_id=0 | -0.parquet -part_id=1 | -0.parquet -... -part_id=N | -0.parquet -... date=2023-01-01 | ...
原本以为可以通过pl.scan_parquet("dataset", hive_partitioning=True).filter(pl.col("part_id") == 0)快速加载所有part_id=0的Parquet文件,但实际Polars会扫描整个数据集(约60000个文件),耗时约40秒;而手动传入文件路径列表调用scan_parquet仅需约0.5秒。
请问是否是我对scan_parquet的用法存在误解?
补充说明:数据集通过多次调用sink_parquet创建,每次传入仅含单个date的Polars DataFrame,仍将date列为分区列,未手动创建文件夹结构。使用的是Linux系统上的Polars 1.31.0版本。
核心原因
这并非用法误解,而是Polars 1.31.0版本处理多层Hive分区时的分区发现逻辑限制:
- 启用
hive_partitioning=True时,Polars需要先遍历整个目录树解析所有分区键(date、part_id),这个过程会扫描所有60000个文件所在的目录,导致耗时过长。 - 分批写入的方式(每次仅传单个date的DataFrame)没有让Polars优化分区发现路径,它依然会全量遍历目录来识别所有分区值。
优化方案
1. 用通配符直接定位目标分区
利用文件系统通配符限定扫描范围,让Polars只处理part_id=0的目录,避免全量遍历:
pl.scan_parquet("dataset/date=*/part_id=0/*.parquet", hive_partitioning=True)
这种方式会直接匹配符合条件的文件路径,耗时和手动传入路径列表接近。
2. 升级Polars版本
Polars后续版本(如1.32及以上)对Hive分区扫描逻辑做了优化,支持在过滤阶段提前跳过不符合条件的分区目录。若允许升级,建议更新到最新稳定版后测试原代码性能。
3. 统一写入数据集
后续若有新数据写入,尽量一次性写入包含所有date的DataFrame;若必须分批写入,确保目录结构一致性,帮助Polars更高效识别分区键。
内容的提问来源于stack exchange,提问作者Skumin

