Spark 为什么会写出大量小文件?怎么控制?
面试怎么回答
Spark 输出文件通常和写入 Task、动态分区数及写入批次有关。我会先看目标分区基数和数据量,再按目标分区 Repartition,合理设置 Shuffle 分区和文件大小;流式任务还要控制微批频率。冷分区可以定期 Compaction,但不会简单 Coalesce(1),因为它会损失并行度。最终用单分区文件数、平均大小和扫描开销做持续监控。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
一个分区一天只有几 GB,落盘后却出现上万个文件。上游任务看起来很快,下游列表、元数据和扫描性能却越来越差。
小文件数量通常由输出分区数、动态分区和写入批次共同决定。
治理 Spark 小文件要从写入并行度、分区基数和提交频率三处下手,而不是落盘后无限 Compaction。
一个 Task 通常对应一个输出文件
写入某个表分区时,每个执行 Task 都可能产生文件。Shuffle 分区设置过大、上游分区未收敛,会直接把文件数放大。
动态分区会再次放大
一个 Task 如果同时写多个业务分区,可能在每个分区下生成文件。高基数分区字段和大量稀疏分区,会让“Task 数乘分区数”迅速膨胀。

流式与频繁微批也会累积
即使每批文件不多,高频写入也会在同一分区持续追加。需要控制触发频率、目标文件大小,或使用支持自动优化与 Compaction 的表格式。
写前收敛,写后治理
写前可按目标分区 Repartition,并根据数据量估算分区数;写后定期合并冷分区。不能盲目 Coalesce(1),否则会牺牲并行度并制造单点。
为什么不能只回答概念?
“误区:写完后统一合并就够了”之所以常见,是因为它在理想数据和单次执行里往往说得通。但生产环境会加入数据规模、异常值、并发、版本、失败重试和下游口径,这些条件一变化,只记住术语就很容易给出错误结论。
所以我会把 一个 Task 通常对应一个输出文件、动态分区会再次放大、流式与频繁微批也会累积、写前收敛,写后治理 串成一条完整链路。最终判断标准不是某个函数或配置是否出现,而是能否稳定得到可解释、可验证、可恢复的业务结果。
落地时我会检查什么?
- 输入边界:确认数据范围、时间口径、主键和异常值是否符合假设。
- 执行过程:观察分区、并行度、状态、Shuffle 或资源变化,验证方案是否真正生效。
- 输出结果:用行数、唯一性、关键指标和样本明细证明结果正确,而不是只看任务成功。
- 失败恢复:说明重跑、回滚、版本兼容和责任人,避免一次成功后无人维护。
这些检查不一定全部写进一条 SQL 或一个任务,但面试时能主动补充,说明你回答的不是背诵题,而是在描述一套可上线的工程方案。
一个典型场景
一个任务按国家、日期、小时三层动态分区,国家字段又包含大量长尾值。即使 Shuffle 只有几百个分区,每个 Task 仍打开多个分区文件。团队最初只加合并任务,结果合并速度赶不上新增速度。最终减少过细分区,写前按核心分区重分布,并设置目标文件大小。
小文件先治理分区设计和写入方式,再谈 Compaction。