对于每天产生数十 GB 甚至上百 GB 日志或指标数据的团队来说,Elasticsearch 集群的存储成本与查询性能往往是一对矛盾。把所有数据都放在 SSD 节点上,半年后的账单会非常难看;但把历史数据直接移到机械盘,又会拖慢偶尔的全文检索。实际上,数据本身有明显的冷热属性:最近几天甚至几小时的索引有大量写入和频繁查询,而一个月前的索引几乎不会更新,查询频率也低很多。Elasticsearch 从 6.6 版本开始把索引生命周期管理(Index Lifecycle Management,ILM)作为内置功能提供,目的就是让索引能够在 hot、warm、cold、delete 四个阶段之间自动流转,配合不同硬件配置的节点,实现真正的冷热分离。

在动手配置 ILM 之前,需要先把集群的节点角色划分清楚。最简单的做法是给热节点打上 data_hot 角色,给冷节点打上 data_cold 角色,还可以根据需要设置 data_warm 和 data_frozen。每个节点可以在 elasticsearch.yml 中配置 node.roles,比如热节点写成 node.roles: [ data_hot, ingest, master ],冷节点写成 node.roles: [ data_cold ]。但仅靠角色并不能阻止分片分配,还需要在节点的配置或启动参数中设置属性,例如热节点设置 node.attr.temperature: hot,冷节点设置 node.attr.temperature: cold。这些自定义属性会被索引的分片分配过滤规则使用,保证索引在正确阶段只落在正确类型的节点上。如果没有这一步,ILM 策略执行时只会修改索引的查询状态而不会真正迁移分片。
另一种常见做法是利用 zone 或 rack 级别的感知属性做更细粒度的调度,不过对于冷热分离,temperature 属性已经足够直观。需要注意的是,冷节点通常使用大容量机械盘或高密度存储服务器,CPU 和内存可以适当降低,但网络带宽不能太差,因为迁移分片时需要跨节点复制数据。另外,冷节点最好也加入 data_content 角色或者保持默认的 data 角色,否则专门用于序列化数据的 data_cold 角色在某些版本中无法直接接收常规索引。
ILM 策略如何定义冷热阶段
ILM 策略本质上是一个描述索引生命周期规则的 JSON 文档,通常使用 Kibana 的图形界面或者通过 REST API 创建。策略中可以指定每个阶段进入的条件、要执行的动作以及优先级。以最常见的时序场景为例,策略会先定义一个 hot 阶段:当索引大小达到 30GB 或者文档数量超过 1 亿时触发 rollover,创建新索引继续写入,旧索引进入 warm 阶段。warm 阶段可以设置索引为只读、强制合并段、缩小分片数,并设置分片分配过滤到 warm 节点。cold 阶段进一步将索引移动到冷节点,并可能关闭索引或改为可搜索快照。最后 delete 阶段设置一个保留天数,到期后自动删除索引。
下面是一个完整的 ILM 策略示例,它演示了从 hot 自动滚动到 warm 再到 cold 最后删除的完整生命周期。通过 Kibana 的 Dev Tools 执行以下请求即可创建策略:
PUT _ilm/policy/logs-cold-hot-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "1d",
"max_size": "30gb",
"max_docs": 100000000
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "3d",
"actions": {
"forcemerge": {
"max_num_segments": 1
},
"shrink": {
"number_of_shards": 1
},
"allocate": {
"number_of_replicas": 1,
"require": {
"temperature": "warm"
}
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "15d",
"actions": {
"allocate": {
"number_of_replicas": 0,
"require": {
"temperature": "cold"
}
},
"set_priority": {
"priority": 20
}
}
},
"delete": {
"min_age": "45d",
"actions": {
"delete": {}
}
}
}
}
}
上面的策略中,min_age 是相对于索引创建时间还是 rollover 之后的新索引?这取决于索引是否被 rollover。对于被管理的索引,ILM 会使用索引的 index.lifecycle.indexing_complete 或原始索引的创建时间作为基准。通常我们建议在写入别名上关联 ILM 策略,这样 rollover 生成的每个新索引都会自动套用同一策略,并且 min_age 是从每个索引自己的创建时间开始计算。因此不会出现所有历史索引在同一时间突然涌入下一个阶段,而是逐个索引到期后自动流转。这也是为什么 ILM 适合大规模时序数据的原因之一。
创建索引模板并关联别名
策略定义好之后,还需要告诉 Elasticsearch 哪些索引要使用这个策略。推荐做法是创建一个索引模板,将 ILM 策略名称和写入别名绑定到模板中。这样新写入的索引会自动匹配模板,并使用别名进行读写。索引模板中还需要设置 index.routing.allocation.require.temperature 的初始值为 hot,确保新索引在刚创建时只分配到热节点上。同时不要在模板中指定具体的索引名,而是使用通配模式,例如 logs-app-*。
下面是一个索引模板的示例,注意模板中同时定义了 settings 和 mappings,并指定了 ILM 策略名称和 rollover 别名:
PUT _index_template/logs-cold-hot-template
{
"index_patterns": ["logs-cold-hot-*"],
"template": {
"settings": {
"index.lifecycle.name": "logs-cold-hot-policy",
"index.lifecycle.rollover_alias": "logs-cold-hot-write",
"index.routing.allocation.require.temperature": "hot",
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"message": {
"type": "text"
},
"level": {
"type": "keyword"
},
"host": {
"type": "keyword"
}
}
}
}
}
建立模板后,需要手动创建第一个索引并指定别名为 logs-cold-hot-write,同时设置 is_write_index 为 true。后续所有写入都通过这个别名进行,rollover 动作会自动把写索引切换到新索引。第一个索引的初始名称可以是 logs-cold-hot-000001,创建请求如下:
PUT logs-cold-hot-000001
{
"aliases": {
"logs-cold-hot-write": {
"is_write_index": true
}
}
}
之后就可以通过 logs-cold-hot-write 别名持续写入数据。当日志量达到 hot 阶段设定的 rollover 条件时,Elasticsearch 会自动创建 logs-cold-hot-000002,并将旧索引的写状态移除。旧索引的 min_age 从它创建那一刻开始计时,到了 3 天之后就会进入 warm 阶段,执行 forcemerge、shrink 和迁移到 warm 节点的动作。这里有一个细节:如果旧索引的主分片数大于 1,shrink 动作需要先分配到一个单独的节点,并且在 shrink 之前会先执行 forcemerge,整个过程会增加一些资源开销。因此如果数据量不大,可以省略 shrink,只保留 forcemerge 和 allocate。
验证索引阶段流转与常见问题排查
配置完成后,可以通过 GET _ilm/status 查看 ILM 是否正常运行,通过 GET logs-cold-hot-000001/_ilm/explain 查看某个索引当前所处的阶段、下一步动作以及可能卡住的原因。如果发现索引一直没有从 hot 流转到 warm,首先检查 index.routing.allocation.require.temperature 的值是否与 warm 节点的 node.attr.temperature 匹配。很多故障都是因为节点属性拼写不一致,例如模板里写的是 hot 而节点上配置的是 data_hot。另外,如果集群里根本没有 warm 或 cold 节点,allocate 动作会一直等待,ILM 会进入 error 或 retry 状态,此时需要先添加对应的节点再重试该索引。
另一个常见的误区是把删除阶段设置得太短,导致索引在滚动发生后很快被自动删除,数据丢失。ILM 的 min_age 是相对于索引创建时间的,而不是相对于进入该阶段的时间。例如 hot 阶段 min_age: 0ms,warm 阶段 min_age: 3d,cold 阶段 min_age: 15d,delete 阶段 min_age: 45d,那么一个索引在创建后第 3 天进入 warm,第 15 天进入 cold,第 45 天被删除。这个时间线是线性的,不会因为前面的阶段延迟而顺延。因此设置保留时间时要充分考虑从写入到删除的总时长,而不是只考虑冷阶段停留多少天。
对于已经存在的大量历史索引,如果希望它们也应用冷热分离策略,不能直接修改策略的 min_age 来追溯处理已经超龄的索引,因为 ILM 只会评估索引创建时间与当前时间的差值,如果已经超过 delete 阶段的 min_age,可能会立即触发删除。这时候应该先调整策略,或者手动将历史索引的 index.lifecycle.name 改为新策略,然后使用 POST /logs-old-*/_ilm/retry 逐步推进。另外注意冷节点上的索引副本数如果设为 0,会降低查询并发能力,但能大幅节省存储。如果冷阶段偶尔有全文检索需求,建议保留 1 个副本或者改用可搜索快照,把索引以快照形式挂载到内存中提供只读查询,代价是首次查询会有额外延迟。
最后需要强调的一点是,ILM 本身不负责节点间的硬件监控或自动扩容,它只根据策略中定义的规则触发分片分配和索引操作。冷热分离的底层依赖 Elasticsearch 的 shard allocation filtering 机制,所以任何情况下都能通过 GET _cluster/allocation/explain 分析分片为什么没有从热节点迁移到冷节点。结合 ILM explain 输出,基本可以定位到是策略没有触发、节点属性不匹配、磁盘水位过高还是分片正在恢复中。掌握这两个排查工具,冷热分离的排障效率会提升很多。
Elasticsearch_ILM冷热数据分离索引生命周期修改时间:2026-08-13 05:10:03