深入 HBase 07 - Compaction、TTL、版本与 Delete
前置问题与边界
本篇回答一个核心问题:不可变 HFile 如何合并,旧值和 tombstone 何时真正消失。
HBase 的写入路径把 mutation 写入 WAL 和 MemStore,flush 后形成新的 HFile。HFile 不原地修改,因此旧版本、Delete 标记和过期数据会散落在多个 StoreFile 中。Compaction 通过重写新文件来减少 StoreFile 数量,并在可见性规则允许时丢弃不再需要的 Cell。
本篇不讨论跨行事务,也不把 Delete 写成“立即删除磁盘数据”。HBase 的 Delete 是写入一个或多个 tombstone;旧数据是否在物理文件里消失,取决于 compaction 类型、版本策略、TTL、快照引用、最小保留时间和扫描可见性。
对象路径图
flowchart TD
P[Put/Delete] --> WAL[WAL]
P --> MS[MemStore]
MS --> FL[Flush]
FL --> H1[HFile A]
FL --> H2[HFile B]
H1 --> MC[Minor Compaction]
H2 --> MC
MC --> H3[New HFile]
H3 --> MA[Major Compaction]
MA --> HC[Cleaner and archive]
MA --> OUT[Less StoreFiles, eligible versions removed]
Minor compaction 主要合并一部分 StoreFile,降低文件数量。Major compaction 会把某个 Store 的文件集合更完整地重写一次,并提供清理 Delete tombstone、过期版本和超过版本上限数据的机会。这里说的是“机会”,不是每次都立即清理所有历史数据。
关键对象和状态
| 对象 | 作用 | 关键状态 |
|---|---|---|
| StoreFile | column family 下的 HFile 包装 | 数量决定读放大和 compaction 压力 |
| MemStore flush | 内存转不可变文件 | 产生新的 StoreFile |
| Delete tombstone | 标记删除范围 | 读路径和 compaction 都要识别 |
| TTL | 按时间过滤过期 Cell | 不生成 tombstone,由读取和 compaction 过滤 |
VERSIONS |
保留最大版本数 | 与 timestamp 和 Delete 共同决定可见值 |
MIN_VERSIONS |
最小保留版本数 | 可能让过期数据暂时保留 |
KEEP_DELETED_CELLS |
删除标记保留策略 | 改变删除后历史数据可见和清理语义 |
| Snapshot | 引用 HFile | 可能阻止旧文件立即删除 |
Delete、TTL 和版本策略是三种不同机制。Delete 会写入 tombstone;TTL 是列族属性,按 Cell timestamp 判断过期;版本策略决定同一 Cell 坐标保留多少历史版本。Compaction 把这些规则放在一起执行。
Delete 先写 tombstone
HBase 的 Delete API 支持删除列的某个版本、某列所有版本、某个列族内的版本范围等语义。删除操作本身也是 mutation,会进入 WAL 和 MemStore,再 flush 成 HFile。读路径遇到 tombstone 时,会用它遮蔽被删除范围内的旧 Cell。
因为旧 Cell 可能在更早的 HFile 中,Delete 不能在写入瞬间跑到所有旧文件里把数据擦掉。真正的物理回收必须等 compaction 读取新旧文件,确认 tombstone 覆盖的数据已经不需要对任何可见读保留,再把不需要的 Cell 不写入新 HFile。
TTL 不等于 Delete
TTL 不会为每个过期 Cell 生成 Delete 标记。TTL 是读取和 compaction 时的过滤规则:超过列族 TTL 的 Cell 对普通读取不可见,并可能在 compaction 中被丢弃。
MIN_VERSIONS 会影响 TTL 清理边界。即使 Cell 超过 TTL,只要还需要满足最小版本数,HBase 可能暂时保留。业务上把 TTL 当作“到点立刻物理删除”会误判空间回收和合规删除时间。
版本清理
同一 (row, family, qualifier) 可以保存多个 timestamp 版本。默认读取通常取最新版本;显式设置版本范围或版本数量时,客户端可以看到历史版本。Compaction 会根据最大版本数、最小版本数、TTL 和 Delete 标记决定哪些 Cell 不再写入新文件。
版本清理的结果也受文件组合影响。如果旧版本和 tombstone 分散在不同 HFile 中,只有把相关文件放到同一次合并上下文里,compaction 才有足够信息做最终清理。minor compaction 不一定覆盖所有文件,因此可能保留仍需等待 major compaction 或后续合并才能清理的数据。
Compaction 的代价
Compaction 把多个不可变文件读出、归并、过滤,再写成新的不可变文件。它减少未来读放大,但会消耗当前磁盘 I/O、CPU、HDFS 带宽和 RegionServer 后台线程。compaction backlog 过高时,前台读写会被 StoreFile 数量、磁盘争用和 flush 压力拖慢。
调大写入缓冲并不能替代 compaction。写缓冲影响 flush 频率,compaction 影响文件数量和历史数据回收。两者都在 LSM 代价模型里,但控制的是不同阶段。
最小实验
UNVERIFIED_RUNTIME:当前任务环境没有目标版本运行条件。以下步骤用于 HBase 2.6.6、Hadoop 3.4.3、JDK 17 环境,不包含伪造输出。
复现实验从一张保留多个版本并设置 TTL 的表开始,再向同一 Cell 写入多个时间戳版本,查看多版本可见性,写入 Delete tombstone 后触发 flush 和 major compaction。命令只描述可执行步骤,不附带未采集的清理时间或文件数量。
1 | |
1 | |
1 | |
写入同一个 Cell 的多个版本后查看结果:
1 | |
1 | |
1 | |
1 | |
删除某一列,并触发 flush:
1 | |
1 | |
再次读取,观察 Delete 对可见性的影响:
1 | |
触发 major compaction:
1 | |
使用 HFile 工具观察 compaction 前后 StoreFile 元信息时,只能记录实际环境中的文件数量和元信息。示例文章不得预填固定数值。
清理实验表:
1 | |
1 | |
1 | |
Java Public API 边界
应用代码通过 Put、Delete、Get 和 Admin 触发或观察相关行为。下面示例只展示 Delete 写入和 Admin major compaction 的 Public API 入口,不保证 compaction 立即完成:
1 | |
UNVERIFIED_RUNTIME:这段代码未在目标依赖下编译运行。majorCompact 是请求后台 compaction,不是同步返回“所有文件已经清理完成”的证明。
失败恢复路径
Delete tombstone 和普通 Put 一样走 WAL。RegionServer 在 Delete 已返回但尚未 flush 时崩溃,恢复依赖 WAL replay。恢复后,tombstone 仍会遮蔽对应范围的旧 Cell。
Compaction 过程中失败时,HBase 不能让新旧文件集合同时以不一致方式暴露给读路径。实现会围绕临时文件、提交、StoreFile 列表和归档清理维护可恢复状态。Snapshot 引用的 HFile 不能简单删除,旧文件可能进入 archive 后继续被 snapshot 访问。
工程迁移
| HBase 机制 | 通用模式 | 迁移场景 |
|---|---|---|
| tombstone | 先标记后回收 | LSM delete、对象存储删除标记 |
| TTL filter | 读时过滤加后台回收 | 缓存过期、日志保留策略 |
| compaction | 后台重写控制读放大 | Lucene segment merge、RocksDB compaction |
| snapshot 引用 | 引用阻止物理删除 | Copy-on-write 文件、备份保留 |
模式公式是:visible_data = latest_versions - covered_by_tombstone - expired_by_ttl,physical_reclaim = compaction(visible_rule, retention_refs)。听到“删除后空间没有下降”,先检查 compaction 和引用保留,不要先怀疑 Delete 没生效。
常见误解
| 误解 | 更准确的说法 |
|---|---|
| Delete 会立即删除所有 HFile 中的数据 | Delete 先写 tombstone,物理回收依赖 compaction |
| TTL 会写入删除标记 | TTL 是时间过滤规则,不为每个 Cell 生成 tombstone |
| major compaction 一定立刻清掉所有旧值 | 清理受版本、TTL、Delete、快照和保留策略影响 |
| minor compaction 和 major compaction 只差名字 | 覆盖文件范围不同,清理能力和 I/O 代价也不同 |
| compaction 只影响磁盘空间 | compaction 同时影响读放大、后台 I/O 和前台延迟 |
练习
- 写入同一 Cell 的 5 个版本,设置
VERSIONS => 3,观察普通 get 与多版本 get 的差异。 - 对同一列执行 Delete 后,在 flush 前后分别读取,区分可见性和物理回收。
- 创建 snapshot 后触发 major compaction,观察旧 HFile 是否立即删除。
- 调整 TTL 与
MIN_VERSIONS,观察过期数据是否仍被保留。
系列导航
参考资料
- Apache HBase Reference Guide:Data Model、Schema Design、Compaction、TTL、Versions:https://hbase.apache.org/docs/
- Apache HBase 2.6 User API:
Delete、Put、Get、Admin:https://hbase.apache.org/2.6/apidocs/ - Apache HBase 源码 rel/2.6.6:compaction、scanner、delete tracker、store scanner:https://github.com/apache/hbase/tree/rel/2.6.6
- Apache HBase JIRA 与 Release Notes:https://issues.apache.org/jira/projects/HBASE
- LSM-Tree 论文:https://www.cs.umb.edu/~poneil/lsmtree.pdf
