1. Compaction概述

1.1 什么是数据湖仓Compaction

先说说我个人的理解。Compaction,中文常翻译为「合并」或「压实」,在数据湖仓里,它干的事其实很简单——把一堆小文件合并成大文件,同时清理掉那些无用的中间数据。

你想想看,数据湖仓底层通常用 Parquet、ORC 这类列式存储格式。每次写入,哪怕只写一条数据,都可能生成一个新文件。日积月累,小文件数量会膨胀到几十万甚至上百万。Compaction 就是专门来收拾这个烂摊子的。

我记得刚接触 Iceberg 时,有个同事问我:「这不就是 Hive 里的合并小文件吗?」嗯,有点像,但又不完全一样。传统 Hive 的合并是离线跑个脚本,而湖仓的 Compaction 是框架自带的、可配置的、增量执行的机制。它更像一个后台管家,默默地把碎片整理好。

一句话定义:Compaction 是数据湖仓中,将多个小文件合并为更大文件、清理过期数据、优化存储布局的自动化过程。

1.2 为什么需要Compaction

说白了,不搞 Compaction,你的湖仓会越跑越慢,存储成本越来越高。我在项目中遇到过几次血泪教训,这里给你列几个核心原因。

  • 小文件爆炸:流式写入、CDC 同步、频繁的 INSERT INTO……这些操作都会产生大量小文件。HDFS 或对象存储对小文件并不友好,NameNode 压力大,读取时也要频繁打开关闭文件。
  • 查询性能下降:一个查询要扫描上千个小文件,光是元数据开销就够受的。Parquet 的列式裁剪、谓词下推在小文件面前基本失效。
  • 存储浪费:每个文件都有元数据块(footer、magic number),小文件多了,这部分冗余占比很高。我见过一个极端案例,小文件占用的存储空间比实际数据多了 30%。
  • 快照膨胀:Iceberg、Hudi 这类湖仓格式每次写入都会生成新快照。如果不合并清理,快照列表会越来越长,时间旅行查询也会变慢。
避坑指南:我曾经接手过一个实时数仓项目,Kafka 入湖的 Topic 有 200 个分区,每 5 分钟写一次,一天下来生成了 5 万多个小文件。第一次跑查询等了 40 秒,而数据量其实只有 200GB。后来加了 Compaction,查询时间降到了 8 秒。这个对比让我彻底记住了 Compaction 的重要性。

1.3 Compaction的核心目标

Compaction 不是万能的,但它有几个明确的目标。我习惯把它们分成三个层次。

目标层次 具体内容 衡量指标
存储优化 合并小文件,减少文件数量;清理过期快照和孤儿文件 文件总数下降率、存储节省比例
性能提升 减少查询时扫描的文件数;提高列式存储的压缩率和索引效率 查询延迟、扫描文件数、IO 吞吐量
数据一致性 保证合并过程中数据不丢失、不重复;支持并发读写 ACID 合规性、写入冲突率

嗯,这里要注意一点。很多人以为 Compaction 只是「把小文件变大」,其实它还有一个隐藏目标——清理垃圾。比如 Iceberg 里的过期快照、Hudi 里的旧版本数据,这些如果不清理,湖仓会越来越臃肿。

1.4 Compaction面临的核心挑战

Compaction 听起来简单,做起来坑不少。我踩过几次,给你总结一下。

  • 资源消耗:Compaction 本质上是重写数据文件,需要大量 CPU 和 IO。如果和查询高峰期重叠,很容易把集群打满。我曾经在凌晨跑 Compaction,结果把 YARN 队列占满了,白天的报表任务全堵了。
  • 并发冲突:湖仓支持并发写入,Compaction 在合并时可能和新的写入冲突。Iceberg 用乐观锁解决,Hudi 用时间线机制,但冲突处理不好会导致 Compaction 失败重试。
  • 策略选择:到底合并到多大?多久合并一次?按分区合并还是全局合并?这些参数调不好,要么合并太频繁浪费资源,要么合并太少效果不明显。
  • 数据一致性:合并过程中如果任务失败,不能留下脏数据。湖仓格式虽然提供了快照隔离,但实现细节上稍有不慎就会出问题。
警告:千万不要在生产环境直接手动删除小文件!我曾经见过有人觉得文件太多,直接 rm 掉一批,结果导致数据丢失,快照链断裂。Compaction 必须通过湖仓框架的 API 来执行,它会保证元数据和数据文件的一致性。

1.5 Compaction在湖仓架构中的位置

为了让你更直观地理解,我画了一张图。这张图展示了 Compaction 在整个数据湖仓写入-读取流程中的位置。

数据湖仓写入-读取流程与Compaction位置 数据源 写入引擎 小文件 Compaction 大文件 查询引擎 说明: 写入引擎生成小文件 → Compaction 将小文件合并为大文件 → 查询引擎读取大文件 Compaction 是写入和读取之间的「缓冲层」,它不参与实时写入,也不直接服务查询,但两者都离不开它。

从这张图你能看到,Compaction 处于写入和读取之间。它不直接参与数据生产,也不直接服务查询,但它是整个湖仓性能的「隐形支柱」。没有它,写入端的小文件会压垮读取端。

1.6 主流湖仓格式的Compaction实现

目前主流的湖仓格式——Iceberg、Hudi、Delta Lake——都实现了 Compaction,但思路略有不同。我简单对比一下。

湖仓格式 触发方式 合并策略 并发控制
Apache Iceberg 手动触发 / 定时任务 按分区合并,支持 rewriteDataFiles API 乐观锁,基于快照隔离
Apache Hudi 自动内联 / 异步 按文件组合并,支持 Clustering 和 Cleaning 基于时间线,写时复制或读时合并
Delta Lake 手动触发 / OPTIMIZE 命令 按分区或文件大小合并 基于事务日志,支持并发写入

我个人习惯用 Iceberg,因为它的快照隔离机制比较干净,Compaction 失败后回滚也很方便。但 Hudi 的自动内联 Compaction 对实时场景更友好,省去了额外调度。Delta Lake 的 OPTIMIZE 命令最简单,适合 Spark 生态的团队。

我的建议:选哪个格式不重要,重要的是理解 Compaction 的原理。原理通了,不管用哪个框架,你都能快速上手。后面几章我会深入讲 Iceberg 和 Hudi 的具体实现,到时候你会看到很多共通的设计思想。

1.7 本章小结

Compaction 是数据湖仓的「清道夫」和「整理师」。它把小文件合并成大文件,清理过期数据,优化存储布局。没有它,湖仓的性能和成本都会失控。

核心挑战在于资源消耗、并发冲突和策略选择。这些坑我在项目里都踩过,后面会一一给你拆解。

嗯,这一章就到这里。记住一句话:Compaction 不是锦上添花,而是湖仓的刚需。