PySpark排序后Parquet文件体积异常增大问题排查与解决
排序后Parquet文件体积反增的原因与解决方法
问题背景
我有一个600MB的CSV文件,包含650万行、10列数据,列内容以ID和求和值为主,其中type列仅有online和offline两个唯一值。直接读取转存为Parquet时文件约60MB,但按type+某ID排序后再存Parquet,体积骤增至300MB——从逻辑上看,排序后type列数据连续,应该更易压缩,结果完全相反。
环境信息:Windows 10、Spark/PySpark 3.4.1、Hadoop 3.0.0、Java 1.8.0_391-b13
测试更新
针对不同编码和写入方式做了测试,结果如下:
不同压缩算法下的文件大小对比
| Dataframe类型 | 未压缩 | lz4 | gzip | snappy | zstd |
|---|---|---|---|---|---|
| 原始未排序Dataframe | 224MB | 77MB | 195MB | 82MB | 67MB |
| orderBy排序后Dataframe | 373MB | 305MB | 195MB | 291MB | 189MB |
| repartitionByRange后 | 373MB | 305MB | 195MB | 291MB | 189MB |
原因分析
- Parquet字典编码的分区行为:Parquet默认对每个分区单独生成列字典。未排序时,
type列的两个值分散在全量数据中,但字典仅需存储两个值,用索引映射所有行,压缩效率极高。排序后,虽然全局type列连续,但Shuffle后的分区可能仍包含两种type值,或每个分区单独生成的字典无法复用全局统计信息,导致字典编码的优势丧失。 - 其他列的分布变化影响压缩:排序后ID和求和值的分布从随机变为连续,这类数据对Parquet的RLE(游程编码)或比特打包编码不友好——随机ID的重复率可能更高(如果有重复),而连续递增的唯一ID几乎没有重复,压缩率骤降。
- Spark Shuffle后的写入策略:
orderBy触发Shuffle后,默认分区数可能导致数据被拆分到多个小分区,Parquet的页大小无法覆盖足够多的连续数据,游程编码无法发挥作用,进一步降低压缩效率。
解决方法
1. 强制全局字典编码并合并分区
将数据合并到单个分区后写入,让Parquet生成全局字典,最大化type列的压缩效率:
df.orderBy("type", "id") .coalesce(1) # 合并为单个分区,650万行数据量无性能压力 .write .option("compression", "zstd") .option("dictionaryEncoding", "true") .parquet("sorted_output.parquet")
2. 按type分区后再排序
先按type分区,确保每个分区内只有一种type值,再对ID排序,此时type列可完全用RLE编码,压缩率会显著提升:
df.repartition(col("type")) .orderBy("id") .write .option("compression", "zstd") .parquet("partitioned_sorted.parquet")
3. 调整Parquet写入参数
优化Parquet的页大小和编码策略,让连续数据能被游程编码充分处理:
df.orderBy("type", "id") .write .option("compression", "zstd") .option("parquet.page.size", "1048576") # 可根据数据量调整页大小 .option("spark.sql.parquet.enableDictionary", "true") .parquet("optimized_output.parquet")
4. 确认数据类型最优
检查ID和求和值的类型,尽量使用数值类型(如Long、Integer)而非字符串类型——数值类型的压缩率远高于字符串,尤其是连续分布的数值。
内容的提问来源于stack exchange,提问作者sevatster
相关产品推荐
相关产品推荐

