组合模板与索引生命周期

讲解 Elasticsearch 组合模板与索引生命周期:组件模板的复用与版本化、组合模板的 composed_of 顺序与优先级合并规则、与 ILM 及数据流的配合、旧式模板迁移路径,以及用 _simulate 验证最终生效配置的排错方法,避免模板体系从减少重复变成制造困惑。

索引模板决定「新索引长什么样」:分片数、副本数、分析器、字段映射、生命周期策略。7.8 之前只有一种模板,所有配置塞在一个大 JSON 里,多个业务共用时靠复制粘贴维护,改一处要同步改十处。组合模板(composable template)把配置拆成可复用的组件模板,再用组合模板按顺序拼装,配合 ILM 形成「模板定结构、ILM 管生命周期」的分工。本文讲清这套机制的合并规则、与 ILM 的配合方式,以及升级迁移时的注意事项。

1. 索引模板的演进

1.1 旧式模板的问题

旧式模板(legacy template)用 PUT _template/<name> 定义,把所有配置写在一个对象里。它有三个硬伤:一是无法复用,两个业务都要「中文分析器」只能各写一份;二是没有组合能力,公共配置与私有配置混在一起;三是冲突时只有「后定义者覆盖」,没有显式的优先级表达。团队规模一大,模板就变成没人敢动的巨石。

1.2 组件模板与组合模板

组合模板体系把配置拆成两层:

  • 组件模板(component template):一段可复用的配置片段,包含 settings、mappings、aliases 中的任意组合。
  • 组合模板(index template):声明 index_patterns 与 composed_of 列表,把若干组件模板按顺序组装,并可再附加自己的 settings、mappings、aliases。

一个索引创建时,Elasticsearch 找出匹配它的所有组合模板,按 priority 选优先级最高的,再按 composed_of 顺序合并组件模板。

1.3 与相关概念的分工

概念作用生效时机
组合模板定义新索引的结构索引创建时
ILM 策略定义索引的生命周期动作索引存在期间
数据流管理按时间滚动的写索引rollover 时
搜索模板复用查询语句查询时
动态映射自动推断字段类型写入新字段时

一个常见混淆是把「组合模板」与「搜索模板」搞混。前者管索引结构,后者管查询语句(用 Mustache 语法),两者毫无关系,只是中文名都带「模板」二字。

2. 组件模板

2.1 定义组件模板

curl -X PUT "localhost:9200/_component_template/base_settings" \
  -H "Content-Type: application/json" -d'
{
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "30s"
    }
  },
  "_meta": {
    "description": "通用分片与刷新设置",
    "version": 3
  }
}'

template 里可以放 settings、mappings、aliases 三者任意组合;_meta 是自由元数据,用来记录用途与版本,不参与合并。

2.2 分析器组件

把分析器定义抽成独立组件,供多个业务复用:

curl -X PUT "localhost:9200/_component_template/zh_analyzer" \
  -H "Content-Type: application/json" -d'
{
  "template": {
    "settings": {
      "analysis": {
        "analyzer": {
          "zh_index_analyzer": {
            "type": "custom",
            "tokenizer": "ik_max_word",
            "filter": ["lowercase", "stop"]
          }
        }
      }
    }
  }
}'

分析器属于 settings,因此可以在多个组合模板间共享。注意分析器变更需要重建索引,组件模板只解决「定义复用」,不解决「重建」。

2.3 映射组件

字段映射是最适合拆分的部分:把 ECS 公共字段、时间字段、业务字段分开定义。

curl -X PUT "localhost:9200/_component_template/ecs_base_mapping" \
  -H "Content-Type: application/json" -d'
{
  "template": {
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "host": {
          "properties": { "name": { "type": "keyword" } }
        },
        "message": { "type": "text" }
      }
    }
  }
}'

properties 的合并是逐字段深度合并:多个组件定义了不同字段会叠加;定义了同名字段则以靠后的组件为准。

2.4 组件模板的版本化

组件模板支持 version 字段做版本标记,但它只是元数据,不影响合并:

{ "_meta": { "version": 3, "updated_by": "platform-team" } }

真正需要版本化的是组件模板本身。推荐做法是每次变更新建一个带版本号的组件(如 base_settings_v2),组合模板引用新组件,观察一段时间后再删除旧组件。这样回滚只需改引用,不用重建旧定义。

3. 组合模板

3.1 声明索引匹配

curl -X PUT "localhost:9200/_index_template/logs_template" \
  -H "Content-Type: application/json" -d'
{
  "index_patterns": ["logs-*"],
  "priority": 200,
  "composed_of": ["base_settings", "ecs_base_mapping", "zh_analyzer"],
  "template": {
    "settings": { "number_of_replicas": 2 },
    "aliases": { "logs": {} }
  }
}'

index_patterns 支持通配符,一个索引可以匹配多个组合模板,靠 priority 决胜。

3.2 composed_of 的顺序与合并规则

合并顺序是:组件模板按 composed_of 数组顺序依次合并,最后合并组合模板自身的 template。后合并的覆盖先合并的。因此把「通用配置」放前面、「特化配置」放后面是基本纪律。

base_settings (shards=3, replicas=1)
  → zh_analyzer (analysis)
    → ecs_base_mapping (mappings)
      → template 自身 (replicas=2)   ← 最终 replicas=2

3.3 priority 与模板冲突

当多个组合模板匹配同一索引:

curl -X PUT "localhost:9200/_index_template/logs_special" \
  -H "Content-Type: application/json" -d'
{
  "index_patterns": ["logs-nginx-*"],
  "priority": 300,
  "composed_of": ["base_settings", "nginx_mapping"]
}'

logs-nginx-2026.01 同时匹配 logs-*(priority 200)与 logs-nginx-*(priority 300),后者胜出。注意不会合并两个组合模板:只有优先级最高的那个生效,其余被完全忽略。这是最容易踩的坑——以为「更具体的模板会叠加,只覆盖冲突部分」,实际是整体替换。

3.4 优先级相同的情况

若两个组合模板 priority 相同且都匹配,Elasticsearch 无法确定用哪个,会拒绝创建索引并报错。因此同一 pattern 空间内必须保证优先级唯一。

3.5 模板与别名的关系

组合模板里的 aliases 会在索引创建时自动挂上:

{
  "aliases": {
    "logs": {},
    "logs-write": { "is_write_index": true }
  }
}

is_write_index: true 用于数据流或 rollover 场景,标记哪个索引是当前写入索引。多个索引挂同一别名时,只有一个是 write index。

4. 与 ILM 的配合

4.1 在模板里挂 ILM

ILM 策略通过 index.lifecycle.name 绑定,模板是绑定的标准位置:

{
  "template": {
    "settings": {
      "index.lifecycle.name": "logs_30d_policy",
      "index.lifecycle.rollover_alias": "logs"
    }
  }
}

rollover_alias 告诉 ILM 在 rollover 时操作哪个别名。两者必须成对出现,只配 name 不配 alias 会导致 rollover 动作失败。

4.2 数据流与模板

数据流(data stream)的模板必须声明 data_stream:

curl -X PUT "localhost:9200/_index_template/logs_ds_template" \
  -H "Content-Type: application/json" -d'
{
  "index_patterns": ["logs-app-*"],
  "data_stream": {},
  "priority": 500,
  "composed_of": ["base_settings", "ecs_base_mapping"]
}'

data_stream: {} 表示匹配到的名称会创建为数据流而非普通索引。数据流自带 rollover 语义,index.lifecycle.rollover_alias 不再需要,ILM 会自动处理。

4.3 生命周期与模板的职责边界

配置归属原因
分片数模板创建时固定,不可改
副本数模板(ILM 可调)可动态调整
分析器模板影响索引结构
字段映射模板影响索引结构
段合并ILM生命周期动作
转冷/删除ILM生命周期动作
分片分配ILM随阶段变化

原则是:结构性的、创建后不可改的放模板;运行期的、随时间变化的放 ILM。把副本数写在模板里作为默认值,ILM 在 warm 阶段再调低,是常见组合。

4.4 rollover 别名与模板的一致性

ILM 的 rollover 会创建新索引,新索引继承的配置来自模板而非旧索引。因此改了模板后,只有新滚动的索引会生效,已存在的索引保持原状。这一点常导致「改了模板但没效果」的困惑——要生效必须等下一次 rollover,或手工重建索引。

5. 版本化与迁移

5.1 模板变更的生效时机

变更对已有索引对新建索引
组件模板内容无影响生效
组合模板 composed_of无影响生效
组合模板 priority无影响生效
动态 mapping 参数部分可改生效

结论:模板是「创建时快照」,改模板不会追溯修改已有索引。需要追溯时只能 _reindex 或重建。

5.2 迁移旧式模板

旧式模板用 _template API,迁移到组合模板要分三步:

# 第一步:查看旧模板
curl -s "localhost:9200/_template/legacy_logs?pretty"

# 第二步:拆分成组件模板
curl -X PUT "localhost:9200/_component_template/legacy_logs_settings" -d @settings.json
curl -X PUT "localhost:9200/_component_template/legacy_logs_mapping"  -d @mapping.json

# 第三步:建组合模板并验证
curl -X PUT "localhost:9200/_index_template/legacy_logs" -d @composed.json

拆分时注意:旧模板的 order 字段对应新体系的 priority,数值语义一致但取值范围不同(旧 order 默认 0,新 priority 默认 100),迁移后要重新校准优先级。

5.3 新旧模板共存

组合模板与旧式模板可以共存,但组合模板优先:若一个索引同时匹配两类模板,只有组合模板生效。因此迁移期间要避免新旧同时匹配同一 pattern,否则会出现「旧模板改了没反应」的情况。稳妥做法是迁移时给新组合模板用稍不同的 pattern 做灰度,验证后再切换。

5.4 版本化发布流程

平台团队维护模板时,建议走这样的流程:

  1. 新增组件模板 xxx_v2,不动 xxx_v1;
  2. 新建组合模板 yyy_v2,composed_of 引用 v2 组件,priority 略高于 v1;
  3. 用 _simulate 验证新旧组合的最终配置差异;
  4. 观察一段时间,确认无异常后删除 v1 组件与组合模板。

这样任何一步都可回退,不会出现「改坏了没有旧版本可用」的局面。

6. 实践与排错

6.1 用 _simulate 预览

创建索引前先模拟,看最终生效的配置:

curl -X POST "localhost:9200/_index_template/_simulate_index/logs-2026.01?pretty"

返回 settings、mappings、aliases 的最终合并结果,以及 overlapping 字段说明有哪些模板匹配了。这是排查「为什么分片数是 5 不是 3」的最快手段。

6.2 查看索引实际继承的配置

curl -s "localhost:9200/logs-2026.01/_settings?pretty"
curl -s "localhost:9200/logs-2026.01/_mapping?pretty"

若与预期不符,先确认索引是哪个模板创建的(index.template 或 index.provided_name),再回看该模板的 composed_of 顺序。

6.3 常见问题

现象原因对策
模板不生效priority 被更高者压制用 _simulate 看 overlapping
配置被覆盖composed_of 顺序反了通用在前、特化在后
报优先级冲突两个模板 priority 相同保证 pattern 空间内唯一
rollover 失败缺 rollover_alias补上并检查别名 write index
改了模板没反应已有索引不追溯等 rollover 或重建索引
组件模板未定义引用名拼写错先 _component_template/<name> 确认

6.4 多租户下的模板组织

多租户场景建议按「平台层 + 租户层」拆分:

平台层组件:base_settings、ecs_base_mapping、common_analyzer
租户层组合:tenant_a_template(priority 400, composed_of 平台层 + tenant_a_mapping)

租户层只定义差异部分,平台层统一维护公共配置。这样新增租户只需建一个映射组件与一个组合模板,不必复制全部配置。租户隔离的完整方案可阅读《多租户隔离》,字段建模细节可阅读《数据建模与 Mapping 设计》。

7. 总结

环节要点
分层组件模板复用配置,组合模板按顺序拼装
合并顺序composed_of 顺序 + 组合模板自身最后合并
优先级多个匹配时只取 priority 最高者,不叠加
与 ILM模板定结构,ILM 管生命周期,rollover_alias 成对配
生效时机模板是创建时快照,不追溯已有索引
迁移旧 order 对应新 priority,注意取值范围差异
版本化新组件新组合,灰度验证后再删旧版
排错_simulate 看最终配置与 overlapping

组合模板的价值在于把「配置」变成「可组装的构件」,让平台团队维护公共部分、业务团队只关心差异。用好它的前提是理解合并顺序与优先级语义——这两点搞错,模板体系会从「减少重复」变成「制造困惑」。索引生命周期策略的细节可阅读《索引生命周期与 rollover》。

延伸阅读

继续阅读

探索更多技术文章

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

全部文章 返回首页

「elasticsearch」更多文章

  1. 批量写入调优与背压
  2. 相关性调优与离线评测
  3. Elasticsearch 与 OpenSearch 对比