索引是 Elasticsearch 里唯一会随时间无限增长的资源。日志、指标、事件类数据每天都在追加,如果不加治理,几个月后集群会被海量小索引、超大分片和重复副本拖垮。ILM(Index Lifecycle Management)把这套治理动作声明成策略,由集群自动执行:什么时候滚动新索引、什么时候合并段、什么时候迁移到冷存储、什么时候删除,全部可配置、可观测、可回滚。本文从生命周期模型讲起,把 rollover、别名切换、分片调整、force merge、shrink 与归档删除串成一条完整的索引治理链路。
1. 索引生命周期管理概览
一句话总结: ILM 把索引从「可写」到「可删」的一生拆成五个阶段,每个阶段挂动作、按条件自动触发,替代人工定时脚本。
1.1 为什么需要生命周期
时序类数据的特点是「只追加、按时间查询、价值衰减」。如果一直往同一个索引写,会同时踩三个坑:单个索引越写越大,分片无法再拆分,重建成本极高;查询时即便只要最近一小时的数据,也要在包含全部历史的索引上过滤;冷数据占着昂贵的 SSD,而热数据又缺少资源。生命周期管理的本质,是按数据年龄把它放到不同成本的存储层,并在每一层做对应的结构优化。
1.2 五个阶段
ILM 定义了五个阶段,按顺序流转:
- hot:正在写入,读写都活跃,使用高性能节点与较多副本。
- warm:不再写入,只读查询,可以合并段、缩减分片、降低副本。
- cold:查询频率很低,迁移到廉价大容量存储,进一步压缩。
- frozen:几乎不查,用可搜索快照只保留索引元数据与少量本地缓存。
- delete:到期删除,释放全部空间。
阶段之间通过 min_age 或动作完成情况推进,索引一旦进入某阶段就按顺序前进,不会回退(除非人工干预)。
1.3 策略的组成
一条策略就是一份 JSON,phases 下每个阶段有 min_age(相对索引创建或滚动的时间)与 actions:
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_primary_shard_size": "50gb", "max_age": "1d" },
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "3d",
"actions": {
"set_priority": { "priority": 50 },
"forcemerge": { "max_num_segments": 1 },
"allocate": { "number_of_replicas": 1 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"set_priority": { "priority": 0 },
"readonly": {}
}
},
"delete": {
"min_age": "180d",
"actions": { "delete": {} }
}
}
}
}
注意 hot 阶段通常同时挂 rollover 与 set_priority,而 min_age 在 hot 阶段一般省略,表示创建即进入。
2. ILM 各阶段详解
一句话总结: 每个阶段的核心动作不同,hot 管滚动写入、warm 管结构收敛、cold 管存储降本、frozen 管极致压缩、delete 管清理。
2.1 hot 阶段
hot 阶段是唯一允许写入的阶段,主要动作是 rollover。除此之外常挂 set_priority 提高恢复顺序优先级,让热索引在节点重启后优先恢复。hot 阶段也可以挂 forcemerge,但对正在写入的索引合并会与写入争抢 IO,一般只在滚动完成后由下一阶段做。
2.2 warm 阶段
warm 阶段索引已只读,是结构优化的最佳时机:forcemerge 把大量小段合并成少量大段,减少查询时的段遍历开销;shrink 把过多分片合并成更少分片,降低每分片的固定开销;allocate 下调副本数;set_priority 降优先级。典型配置是「合并成 1 段 + 缩减到 1 个分片 + 1 个副本」。
2.3 cold 阶段
cold 阶段的数据查询稀少,重点是降成本:可以 allocate 到标记为 cold 的节点(通常是大容量机械盘),可以 searchable_snapshot 把索引转成可搜索快照只保留本地一小部分,也可以 freeze 降低内存占用。readonly 动作防止误写。
2.4 frozen 与 delete
frozen 是成本最低的可查状态,基于快照仓库按需拉取数据,首次查询有额外延迟。delete 阶段最简单,min_age 到点直接删除整个索引。删除是不可逆的,务必与快照策略配合,重要数据保留可恢复窗口。
2.5 阶段动作的通用参数
多数动作支持 min_age 微调与 wait_for_completion。ILM 会串行执行同一阶段的动作,若某动作失败(例如分配无可用节点),索引会停在 ERROR 状态并重试,需要人工 ilm move 或修复条件后重试。
3. rollover 与别名原子切换
一句话总结: rollover 在索引达到体积或年龄阈值时新建后备索引,并通过别名把读写原子地指向新索引,对外永远只有一个入口。
3.1 rollover 的触发条件
rollover 支持多种条件,满足任一即触发:
POST /logs-write/_rollover
{
"conditions": {
"max_age": "1d",
"max_primary_shard_size": "50gb",
"max_docs": 100000000
}
}
max_age:索引年龄上限,适合按天滚动的日志。max_primary_shard_size:主分片总大小上限,8.x 推荐用这个而非总分片大小,因为它不受副本数影响。max_docs:文档数上限,适合文档体积差异大的场景。
3.2 别名的原子切换
rollover 依赖别名。写入别名指向「当前写索引」,滚动时 ES 创建 logs-000002,然后原子地把别名的写指向切到新索引,旧索引仍留在别名下供查询:
PUT /logs-000001
{
"aliases": {
"logs-write": { "is_write_index": true },
"logs-read": {}
}
}
is_write_index: true 标记唯一可写索引。查询时用 logs-read 别名覆盖全部历史索引,写入时用 logs-write,两者分离使滚动对应用透明。
3.3 数据流与 rollover 的关系
如果使用数据流(Data Stream),rollover 由数据流自动管理,无需手工创建索引与别名,写入直接打数据流名。数据流是 8.x 推荐方式,底层仍是「滚动索引 + 隐式别名」,只是把命名与切换逻辑托管给集群。
3.4 手工 rollover 与自动 rollover
自动 rollover 由 ILM 按条件触发;手工 rollover 用 POST /<alias>/_rollover 强制立即滚动,常用于发布前切分或修复异常。手工 rollover 后 ILM 仍接管后续阶段,不会中断策略执行。
4. 分片与副本调整
一句话总结: 分片数决定并行度与固定开销,副本数决定冗余与读吞吐,二者要随数据年龄在生命周期里动态下调。
4.1 分片数的取舍
分片是 Elasticsearch 的最小并行单位,分片太少无法充分利用节点 CPU,分片太多则每个分片都要消耗堆内存与文件句柄。经验值是单分片 10~50GB,集群总分片数控制在「节点数 × 20」以内。写入阶段的索引按预估日增量定分片,进入 warm 后如果分片过多,用 shrink 收敛。
4.2 副本数的动态调整
hot 阶段为抗节点故障与提升读吞吐,副本可以设为 1 或 2;warm 后查询压力下降,可下调到 1 甚至 0(若有快照兜底)。下调副本立即释放磁盘与内存,是冷数据降本最直接的手段:
PUT /logs-000001/_settings
{
"index": { "number_of_replicas": 1 }
}
4.3 分配感知与冷热标签
用节点属性打标签,让 ILM 的 allocate 动作把索引迁到对应层:
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.awareness.attributes": "data"
}
}
配合 index.routing.allocation.require.data: cold 即可强制索引落到冷节点。注意迁移会触发分片重分配与网络传输,应避开业务高峰。
4.4 分配过滤的坑
分配过滤写错属性名会导致分片无处可去,索引变红。变更前先用 _cat/allocation 确认各层节点数量与剩余空间,冷层容量必须能容纳全部待迁数据,否则迁移会卡在 THROTTLED。
5. force merge 与 shrink
一句话总结: force merge 把多段合并成少段提升查询速度,shrink 把多分片合并成少分片降低固定开销,两者都只对只读索引执行。
5.1 段与查询的关系
Lucene 索引由段(segment)组成,每次 refresh 产生新段,段越多查询时需要遍历和归并的段越多,还会拖慢缓存命中。force merge 把段合并成少量大段,并顺带清理已删除文档。
POST /logs-000001/_forcemerge?max_num_segments=1
对只读索引合并到 1 段效果最好,合并过程消耗大量磁盘 IO 与临时空间,务必在 warm 阶段(写入停止后)做。
5.2 force merge 的代价
force merge 是重操作,可能持续数小时并占用大量 IO,期间查询变慢。它不可中断,中断后残留的合并任务会继续。不要在 hot 阶段对大索引做,也不要频繁重复做——合并完再合并没有收益。
5.3 shrink 的原理
shrink 把源索引的分片合并到目标索引,减少分片数。要求源索引只读、所有分片副本位于同一节点,且目标分片数是源分片数的因数:
POST /logs-000001/_shrink/logs-000001-shrunk
{
"settings": {
"index.number_of_shards": 1,
"index.number_of_replicas": 1,
"index.routing.allocation.require._name": null
}
}
5.4 用 ILM 自动 shrink
ILM 的 shrink 动作会先分配全部副本到同一节点、再执行 shrink、最后重新分配。配置只需给出目标分片数:
"shrink": { "number_of_shards": 1 }
目标分片数必须是源分片数的因数,否则 ILM 报错。滚动索引默认分片数一致,缩到 1 永远安全。
5.5 合并与缩减的顺序
推荐顺序是「先 shrink 再 force merge」:shrink 会重新写入段,之后合并一次即可得到最优结构。反过来做会导致合并成果被 shrink 打散,白做一遍。
6. 归档与删除策略
一句话总结: 删除是最彻底的降本,但必须与快照配合形成可恢复窗口;可搜索快照则用极小本地成本保留长期可查能力。
6.1 可搜索快照
cold 与 frozen 阶段可用 searchable_snapshot 把索引转成可搜索快照,本地只保留少量缓存,数据主体存在对象存储:
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": { "snapshot_repository": "s3-repo" }
}
}
首次查询会从对象存储拉取相关段,有额外延迟;重复查询命中本地缓存后恢复正常。这是把「几乎不查但必须能查」的数据成本压到最低的常用手段。
6.2 删除与快照的配合
delete 阶段直接删索引。若数据合规要求保留可恢复窗口,做法是「快照先于删除」:定期快照到对象存储,删除只删本地索引,需要时从快照恢复。快照仓库的生命周期由仓库自身策略管理,与 ILM 解耦。
6.3 删除的常见误配
min_age 是相对滚动时间而非创建时间,理解错会导致数据过早删除。另外删除阶段不会因为分片未分配而暂停,红索引也会被按时删除——如果集群故障期间刚好跨过删除点,可能直接丢失数据,重要数据应留足缓冲期。
6.4 归档到外部存储
对需要长期冷存但不需在线查询的数据,可以用 Logstash 或快照导出到对象存储归档,集群内只留元数据。这样在线集群始终轻量,历史数据需要时再离线恢复。
7. 生命周期运维与排错
一句话总结: ILM 是异步状态机,排错要看索引当前阶段、动作执行记录与错误原因,多数问题出在分配条件与容量不足。
7.1 查看索引生命周期状态
GET /logs-000001/_ilm/explain
返回 phase、action、step、step_info 与 failed_step,是排错第一入口。批量看:
GET /logs-*/_ilm/explain?only_errors=true
only_errors=true 只列异常索引,适合巡检。
7.2 常见错误与修复
shrink失败:目标分片数不是源分片数的因数,或副本未分配到同节点。allocate卡住:目标层节点容量不足或标签不匹配,检查_cat/allocation。rollover不触发:别名未标记is_write_index,或条件未达阈值。- 索引停在 ERROR:修复外部条件后
POST /<index>/_ilm/retry重试。
7.3 手工干预
需要临时跳过阶段用 POST /<index>/_ilm/move/<index> 指定目标阶段与动作。迁移历史数据时也常用它把老索引直接推到 delete。干预前建议先 _ilm/stop 暂停策略执行,避免自动动作与手工动作冲突。
POST /_ilm/move/logs-000001
{
"current_step": { "phase": "hot", "action": "complete", "name": "complete" },
"next_step": { "phase": "delete", "action": "delete", "name": "delete" }
}
7.4 容量与滚动节奏规划
滚动阈值直接决定集群的索引与分片总量。以日增 100GB、阈值 50GB 为例,每天滚动 2~3 次;保留 180 天就是数百个索引,每个索引的分片数必须提前算好。规划公式:总分片数 ≈ 索引数 × 分片数 × (1 + 副本数),控制在堆内存与节点数能支撑的范围内。
7.5 监控 ILM 健康度
监控 ILM 的 ilm_policy 相关指标与 _ilm/status(RUNNING/STOPPING/STOPPED),对 ERROR 索引告警。同时监控各层磁盘水印,冷层写满会连锁触发分配失败。ILM 的异常往往先表现为分片分配异常,两者要联合观察。
8. 总结
| 环节 | 要点 |
|---|---|
| 生命周期模型 | hot/warm/cold/frozen/delete 五阶段,条件触发、单向流转 |
| hot 阶段 | 唯一可写,挂 rollover 与高优先级 |
| warm 阶段 | force merge + shrink + 降副本,结构收敛 |
| cold/frozen | 迁冷存储或转可搜索快照,极致降本 |
| rollover | 按体积或年龄滚动,别名原子切换写入口 |
| shrink | 多分片合并,目标数须为源分片数因数 |
| force merge | 只读索引合并成少段,重操作避开高峰 |
| 归档删除 | 快照先于删除,留足可恢复窗口 |
| 排错 | _ilm/explain 看状态,修复条件后 retry |
索引生命周期是 Elasticsearch 从「能用」到「可运营」的分水岭。把滚动阈值、阶段动作与删除窗口一次性设计好,集群的容量与成本就变成可预测的量,而不是随着数据增长被动救火。理解了 ILM 的状态机,就能读懂索引模板与数据流的绑定方式,这部分可结合《数据建模与 Mapping 设计》中的别名与模板章节。生产环境的冷热分层落地与容量规划,见《集群分片与高可用架构》与《部署运维与备份恢复》。时序场景下 ILM 与数据流的配合,可对照《时序数据与 TSDB》一文。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。