3.2 Compaction:合并的取舍


文档摘要

3.2 Compaction:小文件合并的取舍 本节摘要:Compaction 是 LSM-Tree 的"分期付款"机制——写入时只追加小文件,后台再把小文件合并成大文件,顺带清除超版本与墓碑。本节讲 Minor 与 Major 两档合并的选择算法、写放大/读放大/空间放大的三角权衡,以及生产上"关自动 Major、低峰手动跑"的通行惯例。 为什么必须有 Compaction 第 2 章结束时 Store 里是什么局面:一个列族下 MemStore 加一串 HFile,最新数据在内存,历史数据散落多个文件,同一行键的新旧版本与墓碑分布在不同文件里。不管它,会发生三件事: 读越来越慢:一次点查要打开的文件数线性增长(下一节的读路径会量化); 空间虚高:被覆盖的旧版本、被删除的数据一直占着磁盘;

3.2 Compaction:小文件合并的取舍

本节摘要:Compaction 是 LSM-Tree 的"分期付款"机制——写入时只追加小文件,后台再把小文件合并成大文件,顺带清除超版本与墓碑。本节讲 Minor 与 Major 两档合并的选择算法、写放大/读放大/空间放大的三角权衡,以及生产上"关自动 Major、低峰手动跑"的通行惯例。

为什么必须有 Compaction

第 2 章结束时 Store 里是什么局面:一个列族下 MemStore 加一串 HFile,最新数据在内存,历史数据散落多个文件,同一行键的新旧版本与墓碑分布在不同文件里。不管它,会发生三件事:

  1. 读越来越慢:一次点查要打开的文件数线性增长(下一节的读路径会量化);
  2. 空间虚高:被覆盖的旧版本、被删除的数据一直占着磁盘;
  3. 文件句柄与索引内存吃紧:RegionServer 打开的 HFile 读者对象数 = Region 数 × 列族数 × 文件数,失控时会撑爆堆外内存。

Compaction 的做法是选若干小文件,归并排序重写成一个大文件——输入文件本来各自有序,归并是线性开销。重写过程中顺带完成真正的删除:超龄 TTL、超额版本数、墓碑覆盖的旧值,都在这一步物理消失(补上 2.3 节的伏笔)。

Minor 与 Major 两档

Minor Compaction:选取部分相邻小文件合并,触发性强、单次代价小。挑选算法(默认 ExploringCompactionPolicy)会在"文件数超过阈值(hbase.hstore.compactionThreshold,默认 3)"后,评估各种候选组合,选出"合并收益/成本"最优的一组。它不保证清除墓碑(墓碑要删的数据可能还在未参与合并的文件里)。

Major Compaction:把一个 Store 的所有文件合成一个,并物理清除一切可删数据。默认 7 天触发一次(hbase.hstore.compaction.major 相关周期参数)。代价是重写整个 Store 的数据,IO 与网络(HDFS 副本流水线)压力巨大——大 Region 一次 Major 可能持续数小时。

参数速查(RegionServer 级):

参数 默认 作用
hbase.hstore.compactionThreshold 3 文件数下限,触发 Minor
hbase.hstore.compaction.max 10 单次 Minor 参与文件数上限
hbase.hregion.majorcompaction 7 天 Major 周期,0 表示禁用自动
hbase.hstore.blockingStoreFiles 16 文件数超此值阻塞写入,防止失控

三角权衡:写放大、读放大、空间放大

Compaction 的所有参数本质上都在这三个量之间挪筹码:

  • 写放大:1 GB 数据被反复重写,实际落盘 5 GB,放大 5 倍。Minor 越激进、Major 越频繁,写放大越高——CPU 与磁盘被 Compaction 抢走,前台写入吞吐反而下降;
  • 读放大:一次点查要查的文件数。Compaction 越懒,文件越多,读越慢;
  • 空间放大:旧版本与墓碑占用的额外磁盘。只有 Major 能彻底回收。

三者不可能同时最小,这是 LSM 系统的固有约束(RocksDB、Cassandra 同病)。HBase 默认配置偏保守,留了大量旋钮让用户按负载画像调:

  • 写多读少(日志流水):降低 Minor 频率、Major 拉长周期,接受读放大;
  • 读多写少(画像查询):保持积极的 Compaction,把文件数压住,读延迟优先;
  • 磁盘紧张:定期手动 Major 回收空间,配合 TTL。

⚠️ 常见坑:看到磁盘告警就手动触发全表 major_compaction,结果几十个 Region 同时开跑,HDFS 带宽被打满,前台读写雪崩。手动 Major 必须分批、限流(RegionServer 级还有 hbase.regionserver.throughput.controller 限速参数可用)。

生产惯例:关掉自动 Major

7 天一次的自动 Major 会在不可预测的时刻突然带来 IO 风暴,所以生产环境几乎标配这条配置:

hbase:046:0> alter 'orders', {NAME => 'cf', hbase:047:1* CONFIGURATION => {'hbase.hregion.majorcompaction' => '0'}}

(或全局配置文件里设 0,按表覆盖更常见。)然后在业务低峰用脚本滚动触发,脚本逻辑伪码:

// 低峰期滚动 Major:每次一个 Region,跑完一个再下一个 for (RegionInfo r : tableRegions) { majorCompactRegion(r); waitUntilCompactionDone(r); // 轮询 Compaction 队列为空 sleep(interval); // 每个之间留缓冲 }

判断 Compaction 是否在拖垮机器,看两个监控点(第 7 章详述):Compaction 队列长度、Compaction 占用的磁盘吞吐。队列长期堆积说明合并速度赶不上 Flush 产文件速度,这是"写多于磁盘承受力"的明确信号,该扩容或降写入了。

排队与阻塞的联动

把 2.2 节的阻塞线补完整:hbase.hstore.blockingStoreFiles(默认 16)——当某 Store 文件数超过它且 Compaction 排队未消化,该 Region 写入再次被阻塞。所以"写 hang"有两个上游原因:MemStore 水位(2.2 节)与 StoreFile 堆积(本节),排查时要先看 Web UI 的 Flush 与 Compaction 两个队列再下结论。

💡 关键直觉:把 Compaction 理解成"信用卡分期"——写入时爽快(只追加),账单(放大系数)后付。调优的全部艺术在于安排一个还得起的还款计划:低峰还、分批还、按负载画像定利率。

本节要点回顾

  • Minor 选部分、Major 合全部:只有 Major 物理清除墓碑、TTL 与超版本;
  • 三放大三角:写放大、读放大、空间放大此消彼长,参数即筹码分配;
  • 默认 7 天自动 Major:生产惯例是关闭,改低峰滚动手动执行并限流;
  • blockingStoreFiles=16:文件堆积是写阻塞的第二条通路,与 MemStore 水位并列排查;
  • 队列长度是体温计:Compaction 队列长期积压等于磁盘带宽已超售。

文件整理好了,读路径终于可以登场:下一节把 MemStore、BlockCache 与一串 HFile 的归并过程完整走一遍。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U