索引生命周期与滚动:ILM 阶段、rollover 与别名原子切换

系统讲解 Elasticsearch 索引生命周期管理:ILM 五阶段动作编排、rollover 触发条件与别名原子切换、分片与副本调整、force merge 与 shrink 优化,以及归档删除策略与运维排错。

索引是 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》一文。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「elasticsearch」更多文章

  1. 可搜索快照与冻结层:把冷数据放进对象存储还能查
  2. 分页与深度分页:from/size、search_after、PIT 与 scroll
  3. 嵌套与父子关联查询:nested、join 字段与性能取舍