小文件问题是什么
分布式计算的每个文件都要经历打开、寻址、读取元数据这几步固定开销,文件越小,这些开销占的比例越高。当成千上万个几 KB 的文件堆在一个分区里,任务启动会变慢、NameNode 或元数据服务压力上升、扫描效率明显下降。成因通常是流式小批写入、写并行度远大于数据量、分区粒度过细。治理分两头:写入侧控制文件数量,读侧用合并与自适应机制兜底。
基本信息
| 成因 | 流式小批写入、并行度过多、分区粒度过细 |
|---|---|
| 影响 | 元数据压力、任务启动慢、扫描效率低 |
| 写入侧手段 | 控制并行度、按目标文件大小重分区 |
| 读侧手段 | AQE 自动合并、定期 compaction、小文件合并任务 |
| 理想大小 | 按引擎与存储权衡,通常控制在百兆量级 |
Spark AQE 合并小分区
Spark AQE 合并小分区
spark.sql.adaptive.enabled=true
spark.sql.adaptive.coalescePartitions.enabled=true
spark.sql.adaptive.advisoryPartitionSizeInBytes=64MB
spark.sql.shuffle.partitions=200 # 写前按目标文件大小反推
# 写出时控制文件数:df.repartition(n).write.partitionBy("dt").parquet(path)Hive ORC 合并相邻小文件
Hive ORC 合并相邻小文件
-- 合并单分区内的相邻小文件(仅支持 ORC 表)
ALTER TABLE dwd_order PARTITION (dt='2026-10-11') CONCATENATE;
-- 写入侧自动合并:小文件在 map 端先合并再落盘
SET hive.merge.mapfiles = true;
SET hive.merge.mapredfiles = true;
SET hive.merge.size.per.task = 134217728;
SET hive.merge.smallfiles.avgsize = 67108864;典型使用场景
- 流式接入落湖:每分钟写一次会累积大量小文件,需要周期性 compaction
- 批量回补历史:并行度高导致单分区文件数暴涨,回补后要合并
- 查询变慢排查:先统计目标分区的文件数与平均大小,确认是不是小文件问题
- 分区粒度评估:按业务查询模式确认是否需要小时级分区,避免过度细分
- 表格式维护:Iceberg、Hudi、Delta 都提供小文件合并动作,需要纳入日常调度
治理手段对照
| 手段 | 位置 | 适用 |
|---|---|---|
| 降低并行度/重分区 | 写入任务 | 批任务写出文件数过多 |
| AQE 自动合并分区 | Spark 3.x 查询计划 | 聚合后分区碎小 |
| ORC CONCATENATE | Hive 表维护 | ORC 格式的静态分区 |
| 表格式 compaction | Iceberg/Hudi/Delta | 流式持续写入的表 |
| 定期合并任务 | 调度层 | 需要跨引擎统一的兜底方案 |
按「天+小时+分钟」过多粒度分区是常见制造小文件的方式,分区粒度要与查询模式匹配。
合并带来额外读写,会短暂放大集群负载;流式表通常按写入节奏安排 compaction 周期,并避开业务高峰与关键报表时间点。