深入 Elasticsearch(13):Segment Merge 与 Index Lifecycle Management
上一篇解决了集群协调——master 选举和 cluster state 同步。这一篇进入 segment 随时间增长后怎样管理。
第 02 篇介绍了 segment 是不可变的,写入产生新 segment。随着写入持续,segment 数量会不断增长。segment 太多搜索变慢,删除的文档占据的空间不会自动回收。Segment merge 和 ILM 分别在 Lucene 层和 index 层解决这个存储生命周期问题。
本文抓两个问题:为什么需要 segment merge,以及 ILM 怎样管理索引的生命周期。
Segment Merge
为什么需要 merge
每次 refresh 产生一个新 segment。持续写入的 index 会积累大量小 segment。问题有二:
- 搜索时需要遍历所有 segment。segment 越多,搜索越慢。
- 删除文档只是在
.liv文件中打标记,物理空间不回收。只有 merge 时才真正删除标记文档、回收空间。
1 | |
TieredMergePolicy
ES/Lucene 默认使用 TieredMergePolicy,自动在后台合并 segment。核心逻辑:
- 把 segment 按大小分层(tier)。
- 在同一层中找到可以合并的候选集(通常 10 个左右小 segment)。
- 合并为一个更大的 segment。
- 合并后的 segment 可能进入更高层。
TieredMergePolicy 的分层合并逻辑:小 segment 在底层积累到一定数量后被合并为更大的 segment,合并产物可能继续参与更高层的合并。
flowchart TB
subgraph 写入产生的小 Segment
s0[seg0<br/>2MB]
s1[seg1<br/>2MB]
s2[seg2<br/>2MB]
s3[seg3<br/>3MB]
s4[seg4<br/>2MB]
s5[seg5<br/>3MB]
s6[seg6<br/>2MB]
s7[seg7<br/>2MB]
s8[seg8<br/>3MB]
s9[seg9<br/>2MB]
end
subgraph 第一轮 Merge
m1[merged_A<br/>23MB]
end
subgraph 多轮 Merge 后
m2[merged_X<br/>200MB]
end
s0 & s1 & s2 & s3 & s4 & s5 & s6 & s7 & s8 & s9 --> m1
m1 -.->|与同层其他 segment<br/>继续合并| m2
关键参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
max_merge_at_once |
10 | 一次最多合并几个 segment |
max_merged_segment |
5GB | 合并后的 segment 最大不超过此值 |
segments_per_tier |
10 | 每层允许的 segment 数 |
floor_segment |
2MB | 小于此值的 segment 优先被合并 |
merge 在后台线程执行,受 index.merge.scheduler.max_thread_count 控制并发线程数(默认 = max(1, min(4, CPU/2)))。merge 消耗 I/O 和 CPU,过于激进的 merge 会影响写入和搜索。
Force Merge
手动触发合并:
1 | |
max_num_segments=1 把所有 segment 合并为一个。这会消耗大量 I/O,只应在不再写入的索引上使用(如历史数据索引、日志归档索引)。
在持续写入的 index 上调 force merge 会导致:新写入产生新 segment → 又需要 merge → 无限循环。
删除文档与空间回收
删除一个文档时,ES 在 segment 的 .liv 文件中标记该文档为已删除。已删除的文档在搜索时被跳过,但物理空间仍被占用。
只有 merge 时,已删除文档才真正被排除——merge 生成的新 segment 不包含已删除的文档,旧 segment 的空间被回收。
1 | |
Index Lifecycle Management (ILM)
Segment merge 是 Lucene 层面的优化。ILM 在 index 层面管理索引从创建到删除的完整生命周期,典型用于日志和时序数据。
五个阶段
1 | |
| 阶段 | 特征 | 典型操作 |
|---|---|---|
| Hot | 活跃写入和搜索,需要高性能存储(SSD) | rollover(滚动到新 index) |
| Warm | 不再写入,仍有搜索需求 | force merge, shrink, 迁移到 warm 节点 |
| Cold | 低频搜索,可用较慢存储(HDD) | 迁移到 cold 节点 |
| Frozen | 极低频搜索,数据可部分卸载 | 部分存储在共享存储(searchable snapshots) |
| Delete | 数据不再需要 | 删除 index |
ILM 的五个阶段构成一条单向状态机,index 按 min_age 条件自动向下游迁移:
stateDiagram-v2
[*] --> Hot
Hot --> Warm : min_age 30d
Warm --> Cold : min_age 90d
Cold --> Frozen : min_age 180d
Frozen --> Delete : min_age 365d
Delete --> [*]
Hot : 活跃写入 + 搜索<br/>SSD 存储<br/>rollover 触发新 index
Warm : 只读<br/>force merge + shrink<br/>迁移到 warm 节点
Cold : 低频搜索<br/>HDD 存储
Frozen : 极低频搜索<br/>searchable snapshots
Delete : 删除 index<br/>不可逆
ILM Policy 定义
1 | |
这个 policy 的语义:index 在 hot 阶段持续写入,当 shard 大小超过 50GB 或 7 天后 rollover 到新 index;30 天后进入 warm(force merge + shrink + 迁移到 warm 节点);90 天后进入 cold;365 天后删除。
绑定到 Index Template
1 | |
观察 ILM 状态
1 | |
返回当前 index 所在的 ILM 阶段、进入该阶段的时间、下一步操作等信息。
模式提炼:不可变结构 + 后台压缩 + 分层存储
1 | |
| 系统 | 不可变单元 | 压缩/合并 | 分层存储 |
|---|---|---|---|
| ES / Lucene | Segment | TieredMergePolicy + force merge | ILM (hot/warm/cold/frozen) |
| Kafka | Log segment | Log compaction + retention | Tiered storage (KIP-405) |
| HBase | HFile | Minor/Major compaction | 无内置分层 |
| Cassandra | SSTable | Size/Leveled compaction | 无内置分层 |
| S3 / 对象存储 | Object | 无 | Lifecycle rules (Standard/IA/Glacier) |
工程迁移表
| 概念 | Elasticsearch ILM | Kafka | HBase | S3 Lifecycle |
|---|---|---|---|---|
| 保留策略 | min_age per phase | retention.ms / retention.bytes | TTL per column family | Transition rules |
| 滚动 | Rollover (size/age) | Topic partition rotation | Region split | 无 |
| 压缩 | Force merge | Log compaction | Major compaction | 无 |
| 缩容 | Shrink (减少 shard) | 无 | Region merge | 无 |
| 删除 | Delete action | Log deletion | TTL deletion | Expiration |
常见误解
误解一:merge 让搜索变快所以应该频繁触发。 Merge 确实减少 segment 数量让搜索更快,但 merge 本身消耗大量 I/O。在持续写入的 index 上频繁 force merge 会和写入竞争资源。应该让后台 merge policy 自动工作,只在不再写入的 index 上做 force merge。
误解二:ILM 的 delete 阶段会删除数据。 是的,delete 阶段会真正删除 index。这是不可逆的。确保 min_age 配置正确,或在删除前做 snapshot 备份。
误解三:删除文档后磁盘空间立即释放。 不会。删除只打标记,空间在 merge 时回收。如果一个 index 不再写入且删除了大量文档,需要 force merge 来回收空间。
练习
-
创建一个 index,分 10 批写入数据(每批之间
_refresh),用_segments观察 segment 数量增长。然后_forcemerge?max_num_segments=1,再次观察。 -
删除几条文档,用
_segments查看deleted_docs字段。force merge 后验证deleted_docs变为 0。 -
创建一个 ILM policy,绑定到 index template,用
_ilm/explain观察 index 的生命周期状态。
系列导航
| 上一篇 | 下一篇 |
|---|---|
| 集群协调:Master 选举与集群状态同步 | 性能模型:搜索延迟、写入吞吐与调优思路 |
