Polars跨大Parquet文件乱序连接正确性及底层原理问询
现有3个各含3M条记录的Parquet文件,单个文件大小超出内存容量,内存最多可同时容纳3M条记录,AWS Lambda处理时最多可处理3M条记录后即超时。
处理流程为:通过Polars的scan_parquet将Parquet文件转为Lazy Dataframe,执行join、select、groupby、alias等操作,截取0-1M条记录后collect(),存储至数据库,之后将下一批(各文件2M-3M条记录)传递给下一次AWS Lambda调用。
当各文件中连接列的行顺序不一致时,Polars能否正确执行连接操作?若可以,希望了解其底层实现原理。
示例场景
- file1的LazyDataframe1:col1为
[a1...a1M, b1...b1M, c1...c1M](共3M条) - file2的LazyDataframe2:col1为
[b1...b1M, c1...c1M, a1...a1M](共3M条) - file3的LazyDataframe3:col1为
[c1...c1M, a1...a1M, b1...b1M](共3M条)
首次运行代码:
lazy_df1 = pl.scan_parquet(file1.snappy.parquet) lazy_df2 = pl.scan_parquet(file2.snappy.parquet) lazy_df3 = pl.scan_parquet(file3.snappy.parquet) temp_res = lazy_df1.join(lazy_df2, on='col1', how='left') joined_res = temp_res.join(lazy_df3, on='col1', how='left') ...#select, groupby, alias final_res = joined_res.slice(0, 100000).collect(streaming=True) print(final_res) #此操作能否正确执行?
个人理解:本次运行中截取0-1M条记录时,各文件会取前1M条,即file1取a1...a1M,file2取b1...b1M,file3取c1...c1M,无法正确连接;而若不截取或流式加载全量Dataframe,因内存中有完整数据则可正确连接。
你的理解完全正确,这段代码无法正确执行连接,具体原因和底层逻辑如下:
Lazy模式的查询下推逻辑
Polars的Lazy DataFrame是基于查询计划优化执行的,当你在最终结果上调用.slice(0, 100000)时,查询优化器会自动把这个切片操作"下推"到数据源读取阶段——也就是直接从每个Parquet文件读取前1M条记录,而非先完成全量连接再切片。这就导致你担心的情况:三个文件各自取的前1M条记录,连接列完全不匹配,自然得不到正确的连接结果。Polars连接的底层实现要求
Polars的join操作(无论Lazy还是Eager模式),核心依赖两种逻辑:
- 哈希连接:需要将右表的连接列构建成完整的哈希表,左表逐行匹配哈希表中的键;
- 合并连接:需要先对左右表的连接列做全局排序,再按顺序匹配。
不管哪种方式,都必须访问参与连接的全量数据才能完成。而你的切片操作被下推后,每个数据源只读取了部分数据,无法构建完整的哈希表或完成全局排序,连接自然无法正确执行。
- 流式处理的局限性
虽然你使用了.collect(streaming=True),但流式模式只是解决内存无法容纳全量数据时的分批加载问题,join操作本身无法仅靠部分数据完成正确匹配——因为连接依赖不同数据源中相同键的记录,这些记录可能分布在文件的任意位置,仅读取部分批次无法保证能匹配到对应的数据。
可行的调整方向
- 先对每个Parquet文件按连接列
col1排序或分区,确保相同键的记录集中在同一批次,这样分批次处理时,每个批次内的键能在各文件中找到对应匹配; - 如果Lambda的资源限制允许,先将单个文件的全量数据按连接列预分组后,再分批次执行连接;
- 按
col1的哈希值拆分每个文件为多个小文件,确保相同哈希值的键在同一小文件中,之后按小文件批次执行连接。
内容的提问来源于stack exchange,提问作者xavi4_4_4

