ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Elasticsearch ILM索引生命周期管理实战:从日志滚动到冷热分离

Elasticsearch ILM索引生命周期管理实战:从日志滚动到冷热分离 1. ILM 到底是什么为什么索引会“活不到老”做过 Elasticsearch 运维的同学应该都有过这种经历日志、订单、用户行为这类数据一天一个索引业务刚上线时没什么感觉等跑上两三个月再看集群里堆了几百个索引分片数几千个磁盘占用一天比一天夸张查询越来越慢删也不是不删也不是。我之前在一套日志集群上就吃过这种亏十来个业务线的日志全往 ES 里灌单日增量四五 GB不到三个月节点磁盘告警滚动重启一次要好几个小时后来实在扛不住才认认真真把索引生命周期管理ILMIndex Lifecycle Management整套用了起来。ILM 说白了就是让 Elasticsearch 代替你按既定规则管理索引的一生什么时候保持热数据状态供实时写入和查询、什么时候转入温数据、什么时候进入冷数据、什么时候彻底删除全交给策略自动执行。它是一个内置功能从 ES 6.6 开始引入在 7.x、8.x 里不断成熟不需要额外装插件开箱即用。适合谁只要你的 ES 里存在“随时间不断增长、最终会变旧、旧了可以删掉”的数据ILM 都能派上用场比如业务日志、审计记录、埋点数据、消息轨迹甚至是通过 Canal 从 MySQL 同步过来的流水表数据。这就引出一个问题为什么不用 Elasticsearch 自带的 Delete By Query 或者定时任务手动删索引手动删也能解决磁盘问题但解决不了分片膨胀、段文件碎片、以及冷热数据存储位置差异化的问题。ILM 的价值不是单一动作而是把“创建—滚动—压缩—迁移—删除”整套流程串起来并且全程可视化、可审计、可动态调整策略出了问题还能通过 API 精确看到卡在哪一步。对运维来说这不是省一步操作的问题而是把“救火型”的索引管理变成“规则型”的自动治理。2. ILM 策略设计把索引的一生安排得明明白白2.1 四个阶段与核心动作ILM 把索引生命周期划分为四个阶段hot热、warm温、cold冷、delete删除。每个阶段都可以配置一个 min_age意思是索引创建后至少经过多长时间才进入该阶段。阶段内部可以配置具体动作比如 rollover滚动、shrink收缩分片、forcemerge强制合并段、readonly只读、freeze冻结、searchable_snapshot可搜索快照、delete删除等。需要注意这里索引“创建时间”不是策略挂载的时间而是索引实际 created 的时间。很多人一开始会踩这个坑以为策略应用那一刻才开始计时结果明明 hot 阶段配了 30 天才 rollover新索引刚建好就尝试进入下一阶段实际却一直停在原地就是因为理解错了 min_age 的起算点。Delete 阶段通常是终态动作就是把满足条件的索引从集群里物理删除。如果配置了快照备份建议在删除之前先做一次快照再删否则数据彻底消失。对于不需要长期留存的数据比如 30 天前的原始日志直接删是成本最低的方案。2.2 为什么 rollover 是整套机制的基石rollover滚动是我个人认为 ILM 里最核心的动作。它的思想是不让数据无限写入同一个索引而是写到一个别名下面当索引满足条件后自动创建新索引写入流量平滑切换到新索引。为什么要滚动举一个实际的例子如果日志索引按天生成一天的数据量是 50 GB单个索引分片数如果拍脑袋定了 5 个那每个分片 10 GB还能接受。但如果突然做活动某一天数据量飙到 200 GB索引固定 5 个分片单个分片 40 GB查询和写入都会变慢而且想要临时扩分片几乎不可能。反过来如果按小时生成分片虽然碎一点但索引数量一年下来好几千个管理成本和集群 overhead 也不小。rollover 解决的正是这种矛盾它可以按照 max_size、max_age、max_docs 任意一种或组合条件自动滚动让索引大小控制在一个合理区间内。日志场景常见配置是 50 GB 滚动一次或者满 30 天滚动一次先到先触发。这样每个索引大小基本可控分片数不需要设置得很大后期数据进入 warm 阶段做 shrink 时也方便。2.3 冷热阶段配置的取舍逻辑hot 阶段还可以配置 priority这是不少教程容易忽略的点。priority 影响的是节点故障恢复时索引被恢复的顺序数值越大越优先恢复。热数据 priority 可以设为 100而温数据冷数据逐步降低这样集群在重启后写新数据的索引能更快恢复可用。warm 阶段常见动作为只读、shrink 分片、forcemerge 段合并。为什么要在 warm 阶段设置只读因为索引在 hot 阶段随时有写入无法安全地做压缩和收缩一旦进入 warm数据不再变更就可以放心地把它变成只读然后收缩分片把段文件合并成少数大段释放内存和文件句柄。shrink 之后分片数怎么选一般建议收缩到原来分片数的约四分之一但不要小于 1 个同时要保证收缩后每个分片大小在 20–40 GB 之间太大会影响后续查询太小则浪费分片资源。cold 阶段在传统冷热架构里主要是把数据移动到机械硬盘或冷节点上同时在内存使用上做优化。如果数据量非常大且很少被访问还可以配合 searchable_snapshot把索引数据固化到快照存储本地只保留可搜索的缓存。这一招对“留着怕占地方删了又怕哪天要查”的数据特别有效。3. 从零到一一套完整日志策略的落地实战3.1 场景与目标定义我这次以一套业务日志集群为背景环境是 Elasticsearch 7.10三台数据节点SSD 容量有限日志保留 90 天。目标很清晰索引滚动条件单索引达到 50 GB 或创建满 30 天自动滚动数据 2 天后从 hot 阶段进入 warm进行只读、shrink 和 forcemerge数据 7 天后进入 cold 阶段迁移到机械盘节点数据 60 天后进入 delete 阶段直接从集群删除。这个设计的思路是热数据只保留最近 2 天因为日志查询大多看当天和昨天的2 天到 7 天之间属于“最近这几天可能还要排查问题”频率中等放 warm 阶段用少量分片承载既能查得快又省资源7 天以上基本属于“偶尔翻旧账”直接扔冷节点待着60 天前的数据确认不会再有人看删掉给集群腾地方。3.2 定义策略先创建策略以下 JSON 是直接可以用的完整版本PUT _ilm/policy/log_retention_policy { policy: { phases: { hot: { min_age: 0ms, actions: { rollover: { max_size: 50gb, max_age: 30d }, set_priority: { priority: 100 } } }, warm: { min_age: 2d, actions: { readonly: {}, shrink: { number_of_shards: 1 }, forcemerge: { max_num_segments: 1 }, set_priority: { priority: 50 } } }, cold: { min_age: 7d, actions: { allocate: { number_of_replicas: 0, include: { box_type: cold } }, set_priority: { priority: 0 } } }, delete: { min_age: 60d, actions: { delete: {} } } } } }这段配置里有几个细节值得说明。hot 阶段里 rollover 我同时配置了 max_size 和 max_age意思是谁先满足就触发滚动对于日志这种波动大的数据来说两个条件同时设置更稳妥。shrink 里 number_of_shards 设为 1是因为这个场景下单个日志索引滚动后大概 30–50 GB收缩到 1 个分片后大小也在合理范围内查询性能不会差反而省下大量分片 overhead。cold 阶段我把副本数设为 0这个动作背后的逻辑是冷节点上的数据已经很少被查询了副本对查询性能的贡献很有限而且可以减少冷节点磁盘占用但如果你们有容灾要求这一步要谨慎副本数降为 0 意味着对应数据只有一份节点挂掉后可能丢数据。3.3 创建索引模板并挂接策略策略定义好以后它只是一个“规则”还需要告诉 Elasticsearch“哪些索引用这个规则”。这一步通过索引模板Index Template完成。注意7.x 之后推荐使用索引模板的 index_template 接口老版本才是 _template。PUT _index_template/logs_template { index_patterns: [logs-*], template: { settings: { number_of_shards: 3, number_of_replicas: 1, index.lifecycle.name: log_retention_policy, index.lifecycle.rollover_alias: logs-alias, routing.allocation.include.box_type: hot }, mappings: { properties: { timestamp: { type: date }, level: { type: keyword }, message: { type: text }, service_name: { type: keyword }, trace_id: { type: keyword } } } } }这里最关键的是两个 settingsindex.lifecycle.name指定该模板创建的索引默认应用哪个 ILM 策略index.lifecycle.rollover_alias指定该索引的滚动别名。模板匹配 logs-*意味着所有以 logs- 开头的索引只要创建时没有显式覆盖这些配置都会自动挂上 log_retention_policy 策略和 logs-alias 别名。字段映射我简化成日志常见的几个字段实际项目里按自己的业务结构填写即可。3.4 初始化第一个索引并验证生效很多人挂好策略和模板后直接开始写入数据结果发现 rollover 不触发索引卡在 hot 阶段动不了。原因通常是没有手动创建第一个索引或者第一个索引没有设置 is_write_index。正确做法是手动创建第一个索引并为别名指定写入索引PUT logs-000001 { aliases: { logs-alias: { is_write_index: true } } }这一步做两件事第一确保 logs-000001 创建时就带着 logs-alias 别名并作为写入索引第二后续写入端只需要往 logs-alias 写rollover 发生后ILM 自动把新索引 logs-000002 设为写入索引对业务无感知。验证策略是否生效最直接的两个命令GET logs-000001/_ilm/explain GET _ilm/policy/log_retention_policy第一个命令返回当前索引处于哪个阶段、哪个 action、哪个 step以及这个 step 的状态是 waiting 还是 complete。如果索引在 hot 阶段 waiting 状态说明条件尚未满足如果卡在某个 step 报错step_info 里一般会有详细信息。第二个命令用于查看策略当前定义的完整内容在调整 min_age 或者新增阶段时非常有用。4. 常见问题与排查技巧实录4.1 索引一直卡在 hot 阶段不滚动这个问题非常典型。索引进入 hot 阶段后一直 waiting 在 rollover 条件判断上等待期可能长达几周表面看起来“策略没生效”。排查路径是这样的先看 GET logs-000001/_ilm/explain 输出确定它是否还在 hot 阶段并且 role 是 write。然后检查是否有写入索引的别名以及日志索引是否长时间没有新数据写入。rollover 的 max_size 是以主分片大小为准的如果一个索引 30 天没有写入自然永远不会触发 max_size又因为 max_age 是从索引创建时间起算的通常 30 天后会滚动所以这种情况一般不常见。更常见的是业务写入端一直在写小时级/天级索引比如每天创建 logs-2026.01.01这些索引数量巨大但大小很小es 里已经有一堆 logs-* 索引别名却一直没有正确绑定到某一个索引上导致 ILM 不知道何时该滚动。解决办法是检查模板和索引名是否匹配、是否一次性把策略挂到了所有索引上。如果新写入的索引确实是通过 logs-2026.01.01 这种命名建议改为固定的 logs-000001、logs-000002 这类递增命名滚动别名才能持续工作。如果已经有一堆固定后缀索引也可以把它们的生命周期设置为基于时间删除的简单策略或者手动迁移。4.2 min_age 起算时间理解错误我最开始用 ILM 时踩过这个坑以为 min_age 是从“进入上一个阶段的时间”开始计算。比如 hot 阶段 2 天后进入 warmwarm 阶段再配 7 天后进入 cold我以为是 hot 结束再等 7 天进 cold实际却是从索引创建时间开始算的。也就是说如果索引创建后 2 天进 warmwarm 配了 7 天 min_age那在第 7 天它就直接进入 cold 了warm 阶段实际只停留了 5 天。所以设计 min_age 时要整体思维不要每个阶段都把时间叠加上去。日志场景建议直接按“数据产生多久之后该进入这个阶段”来理解比如数据产生 7 天后进 cold就配 min_age: 7d不用管前面经历了几个阶段。4.3 shrink 缩小分片失败索引不是只读shrink 动作执行前必须先让索引变成只读否则 Elasticsearch 不允许收缩分片。很多策略里漏掉 readonly 动作或者手动把索引设成可写就会导致 shrink 一直失败。另一个隐藏坑是shrink 后分片数必须是原始分片数的因数。如果你原始索引是 5 个分片shrink 到 2 个分片就一定会报错因为 5 不能被 2 整除。建议要么设置成 1 或者 5要么在最初的模板里就把分片数设成 3、6、9 这类方便整除的数字。我实际项目里如果后续要 shrink 到 1 个分片那原始分片数选多少都无所谓如果打算收缩到 3 个分片那原始分片数最好是 6、9、12 这种倍数关系不然根本收不动。4.4 forcemerge 把集群压垮forcemerge 是一个 IO 密集型操作它会读入索引的所有分片数据重写出非常少的大段。如果同时有多个索引进入 warm 阶段并且 forcemerge 一起执行磁盘 IO 会被打满热数据写入和查询都可能受到拖累。解决思路有两个方向。一是把 max_num_segments 调大一点比如合并到 3 个段而不是 1 个段查询性能损失不大但 IO 压力明显降低。二是在业务低峰期执行比如凌晨 2 点之后这要求策略的 min_age 和你的业务时间对齐别让十几个大索引都在白天高峰期触发 forcemerge。如果集群规模很大还可以借助集群层面的 ILM 执行顺序让各索引的 min_age 错开一点避免“踩踏”式合并。5. 几个容易忽略的设计细节ILM 管理的不只是“删数据”还有“数据在哪里”。如果你们用的是冷热分离架构光配置 hot、warm、cold 阶段还不够还需要在节点上打标签。比如 hot 节点上设置 node.attr.box_type: hot冷节点上设置 node.attr.box_type: cold然后在模板或策略的 allocate 动作里引用这个标签数据才能真正落到对应的节点上。否则即使阶段切换了数据可能还是留在原来的节点上冷热分离形同虚设。另外统一命名规范非常重要。ILM 策略靠索引名前缀匹配模板所以尽量让一类数据使用同一个前缀如 logs-、metrics-、audit-*同时避免使用带日期的动态后缀作为滚动索引名。我见过有的业务直接按天建索引logs-20260101、logs-20260102这类索引一旦很多ILM 很难有效管理因为 rollover 别名只对固定模式的滚动索引有效。建议从一开始就用 logs-000001 这样的递增序号哪怕人工看起来不直观但机器管理和滚动都方便得多。还有一点要提醒修改 ILM 策略时已经应用该策略的索引并不会立刻被强制更新。ES 会异步重新评估所有处于 waiting 状态的索引通常几秒钟内会反映出来。如果修改了策略想立刻让某索引按新规则执行可以先调用 POST /logs-000001/_ilm/remove 移除策略再重新把策略挂上去强制它重新开始评估。这个操作会重置索引当前所处的阶段要谨慎使用生产环境尽量在业务低峰期操作。6. ILM 周边与写入链路、同步工具和查询场景的搭配很多团队不是直接写 ES而是通过消息队列异步写入或者用 Canal 之类的工具把 MySQL 数据同步到 ES。在这些链路里ILM 同样能发挥不小的作用。Java 异步写入 ES 的场景最常见的问题就是写入端只盯着一个索引名写比如写 logs-20260101第二天再写 logs-20260102。这种模型下如果某天数据量特别大单个索引会被撑到几百 GB查询和滚动都很难受。比较好的做法是写入端只写固定别名 logs-alias由 ILM 的 rollover 根据大小/时间自动切换底层索引。这样写入端代码完全不用关心索引名变化索引治理全部交给 ES 自己完成。Canal 同步 MySQL 到 ES 也有类似的问题。同步数据往往是按表名建索引比如 orders、users这些索引不像日志那样无限增长但仍然存在历史数据不断膨胀的问题。对这类索引可以通过 ILM 设置长期保留策略比如 hot 阶段 180 天、delete 阶段 365 天再配合快照备份既能保证历史数据可查又不会让集群无限膨胀。要注意的是rollover 对 Canal 同步场景意义不大因为代码通常直接写固定索引名滚动后业务可能不知道该往新索引写这种场景可以只配置 hot→delete 两个阶段或者用基于时间创建索引但配合定期删除策略。至于查询端如果你们遇到过“ES 向量检索时间太长”的问题除了在向量字段类型、HNSW 参数上做优化外还可以从 ILM 角度做一层收益把最新、最活跃的数据留在 hot 阶段的高性能节点把历史向量索引迁移到冷节点或压缩为只读。查询向量时优先命中热索引冷索引通过别名统一路由也可以明显减少整体检索耗时。这种优化虽然不是 ILM 直接带来的但它为查询性能提供了底层数据分布上的保障属于“数据治理反哺查询性能”的典型做法。使用这套策略一段时间后我的感受是ILM 真正让人舒服的地方不在于某一项自动化而是它把索引治理从“救火式运维”变成“规则式治理”。你只需要把自己的业务需求翻译成阶段和动作剩下的执行、状态追踪、异常反馈都由 ES 来负责。对于那些长期被索引数量失控折腾的团队来说尽早把 ILM 用起来算是一个性价比非常高的决定了。最后分享一个小操作习惯每次创建或调整 ILM 策略后我都会立刻用 GET _ilm/policy/策略名 确认配置落盘再用 explain 看一个实际索引的阶段状态确认它走到了预期位置。这种“配置后立刻验证”的习惯能帮你避免很多“以为配好了其实没生效”的情况。
返回列表