小文件太多导致下游Hive查询变慢——Spark写出文件怎么控制?
先说结论
小文件的核心原因是:Spark的每个Task写一个文件。200个Task = 200个文件。解决方案是写入前用coalesce()或repartition()控制分区数。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
小文件问题是大数据平台的“慢性病”。Spark作业跑得快但写出了几千个小文件,下游Hive查询时HDFS的NameNode要管理这些文件的元数据,每次查询都要打开几千个文件,很慢。今天把产生原因和解决方案讲清楚。
为什么会产生小文件?
场景一:Shuffle后分区数太多
spark.sql.shuffle.partitions默认200,如果最终写入的数据量只朎100MB,就会生成200个文件,每个只有500KB。
场景二:动态分区写入
按日期+国家写入时,每个Task为每个分区组合生成一个文件。200 Task × 30个国家 = 6000个文件!
解决方案
方案一:写入前coalesce()减少分区数
// 写入前合并分区
df.coalesce(10) // 合并成10个分区,生成10个文件
.write.mode("overwrite")
.parquet("hdfs:///data/output")
// coalesce vs repartition:
// coalesce:只减少分区,不触发Shuffle,但可能数据不均匀
// repartition:可增可减,触发Shuffle,数据均匀但多一次Shuffle开销
经验法则:写入后每个文件的目标大小是128MB-256MB。比如总数据量1GB,那coalesce(4)到coalesce(8)就合适。
方案二:开启AQE自动合并
Spark 3.0+的AQE可以自动合并Shuffle后的小分区,减少输出文件数。但注意:AQE只能合并Shuffle后的分区,如果你的作业没有Shuffle(比如纯的filter + write),AQE帮不了你,仍然需要手动coalesce()。
方案三:Databricks的Optimize命令
-- Databricks专有:小文件合并
OPTIMIZE table_name;
-- 还可以同时做Z-Ordering(加速查询)
OPTIMIZE table_name ZORDER BY (country, dt);
我在出海硬件公司用Databricks时,每天ETL完成后都会跑一次OPTIMIZE,合并小文件 + Z-Ordering加速查询。查询延迟降低了70%的核心贡献者之一就是这个。
总结
小文件的核心原因是分区数太多。解决方案:写入前coalesce()、AQE自动合并、Databricks OPTIMIZE。目标:每个文件128-256MB。面试时能说清楚产生原因、多种解决方案、和“查询延迟降低70%”的实战效果,就很完整。
下一篇聊一个运维场景:Spark on YARN的cluster模式和client模式到底有什么区别?生产环境为什么必须用cluster?
你们生产环境的小文件怎么治理的?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长