灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

  • Redis

  • 高性能KV

  • TiDB

  • Elasticsearch

    • Elasticsearch 运维知识地图:从安装配置到排障加速恢复
    • Elasticsearch 集群安装:RPM 裸机生产部署 vs docker-compose 快速起集群
    • 给Elasticsearch集群添加用户密码
    • Elasticsearch 分片和副本:容量怎么规划,改分片数为什么这么麻烦
    • Elasticsearch集群节点磁盘使用分配不均解决办法
    • Elasticsearch 索引模板与映射:Composable 模板、mapping 与动态模板
      • 1. 概念关系与生效时机
      • 2. Composable Index Template
        • 2.1 创建模板
        • 2.2 组件模板(Component Template)
        • 2.3 模板优先级与冲突解决
        • 2.4 Legacy Index Template(已废弃,了解即可)
      • 3. Mapping 定义
        • 3.1 显式创建索引时定义 mapping
        • 3.2 在已有索引上添加字段
        • 3.3 text 与 keyword 的取舍
      • 4. 动态模板(Dynamic Templates)
        • 4.1 创建带动态模板的索引
        • 4.2 匹配规则表
        • 4.3 优先级顺序
      • 5. 坑与边界
        • 5.1 模板与动态模板的生效范围
        • 5.2 字段类型锁定
        • 5.3 动态映射的默认值风险
        • 5.4 字段爆炸与 dynamic 的三种取值
        • 5.5 Priority 未设置时默认 0
        • 5.6 通配符冲突
        • 5.7 _mapping API 无法更新动态模板规则
      • 6. 可复用要点
      • 7. 验证
    • Elasticsearch 分页查询三种方案:from/size、search_after、scroll 怎么选
    • Elasticsearch字符串搜索方式
    • Elasticsearch使用wildcard字段模糊匹配
    • Elasticsearch 数据迁移方案对比:esm vs Logstash
    • Nginx Mirror 模块实现三套ES写入网关
    • ES排障两件套:慢查询日志阈值配置 + tcpdump 抓包看真实请求
    • ES 集群恢复太慢?三个参数加速节点/分片恢复
    • Elasticsearch 常用 DSL 语句(速查表)
    • ES 集群 Yellow 复盘:1023 个副本永远分配不出去,问题不在磁盘
  • 数据管道

  • 其他数据库

  • 数据库
  • Elasticsearch
灯下哥谭
2022-03-17
目录

Elasticsearch 索引模板与映射:Composable 模板、mapping 与动态模板原创

在 Elasticsearch 中,字段类型一旦写入就锁定无法修改。这意味着第一条文档的字段类型决定了整个索引的 mapping——如果 ES 自动推断错了类型,后续查询和聚合都会受影响。索引模板与动态模板存在的理由,就是在文档写入之前就确定正确的字段类型,而不是靠 ES 瞎猜。

版本说明

本文原写于 2022-03,2026-09 重写。
_template API(Legacy Index Template)已在 7.8 起废弃,8.x 移除;本文基于 Composable Index Template(_index_template)与 Dynamic Templates。

# 1. 概念关系与生效时机

概念 作用 生效时机
Composable Index Template 定义新建索引的默认 settings、mappings、aliases 匹配到 index_patterns 时自动应用
Component Template 把公共配置抽取为可复用单元 被 Index Template 通过 composed_of 引用
Mapping 定义字段类型(text/keyword/date 等)和分析器 字段首次写入时生效,或显式 PUT mapping
Dynamic Template 字段首次出现时按规则自动映射类型 新字段首次写入且未被显式 mapping 定义时

四层关系:Index Template(顶层)→ 引用 Component Template → 包含 Mapping 定义 → Dynamic Template 处理未定义的新字段。

# 2. Composable Index Template

# 2.1 创建模板

PUT _index_template/log_template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 2,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "timestamp": { "type": "date" },
        "message": { "type": "text" },
        "level": { "type": "keyword" }
      }
    }
  },
  "priority": 500,
  "composed_of": ["base_mappings"],
  "version": 1,
  "_meta": {
    "description": "日志索引模板"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

关键字段说明:

  • priority:数值越大优先级越高,覆盖其他匹配同模式的模板
  • composed_of:引用组件模板(见下文),实现配置复用
  • template:内联的 settings/mappings/aliases(7.8+ 新结构)

# 2.2 组件模板(Component Template)

把公共配置抽取为可复用组件:

PUT _component_template/base_mappings
{
  "template": {
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

索引模板通过 composed_of: ["base_mappings"] 引用。

# 2.3 模板优先级与冲突解决

当多个模板匹配同一索引名时:

  1. 优先比较 priority:数值大的胜出
  2. 优先级相同:创建时间晚的胜出(不推荐依赖,应显式设置 priority)
# 检查某索引将应用哪个模板
GET _index_template/logs-2024-01
1
2

# 2.4 Legacy Index Template(已废弃,了解即可)

7.8 之前的 _template API:

# ⚠️ 废弃命令,仅作对比
PUT _template/legacy_template
{
  "index_patterns": ["logs-*"],
  "settings": { ... },
  "mappings": { ... }
}
1
2
3
4
5
6
7

与 Composable 的核心区别:

  • Legacy 模板合并所有匹配模板的设置(隐式叠加,易混乱)
  • Composable 只应用 priority 最高的那个(显式覆盖,更清晰)

升级建议:

  • 存量 Legacy 模板建议逐步迁移到 Composable
  • 混合使用时,Composable 默认优先于 Legacy(可通过 priority 控制)

# 3. Mapping 定义

Mapping 定义字段类型,字段一旦写入即锁定类型,不可修改。

为什么字段类型改不了:倒排索引在写入时就已经按该字段的类型和分析器完成了分词与编码,类型是「已经写进磁盘结构里的事实」,不是一层可以重新解释的元数据。所以改类型只能通过 reindex 重建索引。这也解释了为什么动态模板只对首次出现的字段生效——一旦字段的类型被确定并写入,后续的动态模板规则不再适用。

# 3.1 显式创建索引时定义 mapping

PUT my_index
{
  "mappings": {
    "properties": {
      "user": { "type": "keyword" },
      "content": { 
        "type": "text",
        "analyzer": "standard"
      },
      "created_at": { "type": "date" }
    }
  },
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.2 在已有索引上添加字段

PUT my_index/_mapping
{
  "properties": {
    "new_field": { "type": "integer" }
  }
}
1
2
3
4
5
6

限制:

  • ✅ 可以新增字段
  • ❌ 不能修改已有字段的类型(需 reindex)
  • ❌ 不能删除字段(可用 ignore_malformed 或 reindex)

# 3.3 text 与 keyword 的取舍

text 类型会经过分析器分词,支持全文检索(如 match 查询),但默认不能用于聚合排序;keyword 类型不分词,保存原始整值,支持精确匹配(term 查询)和聚合排序。当一个字段需要两种能力时,可以使用 multi-fields 配置让字段同时具备 text 和 keyword 两种类型:

{
  "properties": {
    "title": {
      "type": "text",
      "analyzer": "standard",
      "fields": {
        "raw": { "type": "keyword" }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11

查询时用 title 做全文搜索,聚合时用 title.raw 做精确聚合。代价是该字段会存储两份倒排索引,占用更多磁盘空间。

# 4. 动态模板(Dynamic Templates)

动态模板在字段首次出现时按规则自动映射类型,只对新字段生效。

# 4.1 创建带动态模板的索引

PUT /my_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      },
      {
        "custom_text_fields": {
          "match": "text_*",
          "unmatch": "*_raw",
          "mapping": {
            "type": "text",
            "analyzer": "standard"
          }
        }
      },
      {
        "path_based_geo": {
          "path_match": "location.*",
          "mapping": {
            "type": "geo_point"
          }
        }
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 4.2 匹配规则表

规则 说明 示例
match 字段名通配符匹配 "match": "custom_*" 匹配 custom_name
unmatch 排除特定字段名 "unmatch": "*_raw" 排除 raw 结尾字段
match_mapping_type 按 JSON 类型匹配 "match_mapping_type": "string"
path_match 按字段路径匹配 "path_match": "location.*" 匹配 location.lat
path_unmatch 按路径排除 "path_unmatch": "*.raw"

# 4.3 优先级顺序

执行顺序:按 dynamic_templates 数组中的定义顺序,第一个匹配的模板生效。

{
  "dynamic_templates": [
    { "specific_rule": { "match": "name", "mapping": { "type": "keyword" } } },
    { "generic_rule": { "match_mapping_type": "string", "mapping": { "type": "text" } } }
  ]
}
1
2
3
4
5
6

上例中,字段 name 会匹配 specific_rule 而非 generic_rule。

重要限制:动态模板只影响首次遇到的字段。若 name 已在第一个文档中被动态映射为 keyword,后续即使修改动态模板也无法改变该字段类型,除非重建索引。

# 5. 坑与边界

# 5.1 模板与动态模板的生效范围

  • 模板只对新索引生效:修改模板后,存量索引不会自动变更;需 reindex 或滚动新建索引
  • 动态模板只对新字段生效:已有字段的类型一旦确定,动态模板无法重刷
  • 两者的"只对新 X 生效"特性是设计约束,不是 bug

# 5.2 字段类型锁定

  • 字段类型一旦写入即锁定在索引生命周期内不可变
  • 如需修改类型,唯一办法是 reindex 到新建索引

# 5.3 动态映射的默认值风险

若字段未被模板定义,ES 会根据字段值自动推断类型:

  • 字符串 → text + keyword 子字段
  • 数字 → long 或 float
  • 日期格式字符串 → date

生产环境建议:关闭动态映射或显式控制,避免字段爆炸:

PUT my_index
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "known_field": { "type": "keyword" }
    }
  }
}
1
2
3
4
5
6
7
8
9

dynamic: strict 表示遇到未定义字段直接拒绝写入,而非自动推断。

# 5.4 字段爆炸与 dynamic 的三种取值

dynamic 参数控制新字段的处理策略,有三种取值:

  • dynamic: true(默认):任意新字段自动建 mapping,这是字段爆炸的根源。日志类数据里一个不受控的 JSON 字段就能把 mapping 撑到 index.mapping.total_fields.limit(默认 1000)上限,之后整个索引写入报错
  • dynamic: false:新字段仍会被存下(出现在 _source 中),但不建索引,无法被搜索
  • dynamic: strict:遇到未定义字段直接拒绝写入并报错

生产环境的日志索引建议设置为 strict 或 runtime,配合显式动态模板控制字段数量,防止不受控的数据结构撑爆 mapping。

# 5.5 Priority 未设置时默认 0

与系统模板冲突时可能意外被覆盖,建议始终显式设置 priority。

# 5.6 通配符冲突

多个动态模板可能匹配同一字段,顺序决定结果,建议精确规则在前、宽泛规则在后。

# 5.7 _mapping API 无法更新动态模板规则

PUT /index/_mapping 添加的是字段映射,不是更新动态模板规则。动态模板只能在创建索引时定义,或在关闭索引后重建。

# 6. 可复用要点

  1. 7.8+ Always use _index_template(Composable),弃用 _template(Legacy)
  2. 用 priority 显式控制模板覆盖关系,不要依赖创建时间
  3. 公共配置抽取为 _component_template,通过 composed_of 复用
  4. Mapping 变更受限,生产索引提前规划字段类型;动态模板规则需在索引创建时确定
  5. 生产环境建议 dynamic: strict 或配合显式动态模板,防止字段爆炸

# 7. 验证

# 1. 查看现有 Composable 模板列表
GET _index_template

# 2. 查看特定模板详情
GET _index_template/log_template

# 3. 查看某索引实际应用的 settings/mappings
GET my_index/_settings
GET my_index/_mapping

# 4. 模拟模板匹配(预览某索引名将应用哪个模板)
GET _index_template/logs-test-index

# 5. 预演模板命中结果(检查设置是否如预期)
GET _index_template/_simulate_index/my-test-index-2024-01

# 6. 查看组件模板
GET _component_template

# 7. 创建带动态模板的索引并验证
PUT /test_dynamic_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": { "type": "keyword" }
        }
      }
    ]
  }
}

POST /test_dynamic_index/_doc
{
  "name": "John",
  "age": 30
}

GET /test_dynamic_index/_mapping
# 预期:name 为 keyword 类型(由动态模板设置),age 为 long(动态映射默认推断)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

索引模板与映射快速参考
{
  "_meta": {
    "doc_version": "1.0",
    "article_id": "es-index-template-mapping",
    "profile_context": "elasticsearch-schema",
    "last_updated": "2026-09"
  },
  "quick_start": {
    "create_index_template": "PUT _index_template/<name> {\"index_patterns\": [\"logs-*\"], \"template\": {...}, \"priority\": 500}",
    "create_component_template": "PUT _component_template/<name> {\"template\": {...}}",
    "create_mapping_explicit": "PUT /<index> {\"mappings\": {\"properties\": {...}}}",
    "add_field_to_existing": "PUT /<index>/_mapping {\"properties\": {\"new_field\": {...}}}",
    "create_dynamic_template": "PUT /<index> {\"mappings\": {\"dynamic_templates\": [{\"rule_name\": {\"match\": \"*\", \"mapping\": {...}}}]}}"
  },
  "safety_rules": [
    {"risk": "模板只对新索引生效", "action": "修改模板后存量索引不变,需 reindex 或滚动新建", "scope": "index_template"},
    {"risk": "动态模板只对新字段生效", "action": "已有字段类型锁定,无法通过修改动态模板重刷", "scope": "dynamic_template"},
    {"risk": "字段类型一旦写入即锁定", "action": "如需修改类型必须 reindex 到新建索引", "scope": "mapping"},
    {"risk": "动态映射默认推断可能不符合预期", "action": "生产环境用 dynamic: strict 或显式动态模板", "scope": "mapping"},
    {"risk": "Priority 默认 0 可能被系统模板覆盖", "action": "始终显式设置 priority", "scope": "index_template"},
    {"risk": "动态模板顺序决定匹配结果", "action": "精确规则在前、宽泛规则在后", "scope": "dynamic_template"}
  ],
  "verification": {
    "list_templates": "GET _index_template",
    "get_template": "GET _index_template/<name>",
    "simulate_match": "GET _index_template/_simulate_index/<index_name>",
    "check_index_settings": "GET <index>/_settings",
    "check_index_mapping": "GET <index>/_mapping",
    "list_component_templates": "GET _component_template"
  },
  "version_notes": {
    "legacy_template": "_template API 已废弃,7.8+ 使用 _index_template",
    "composable_priority": "Composable 模板通过 priority 显性控制覆盖"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#架构设计#Elasticsearch
上次更新: 9/11/2026

← Elasticsearch集群节点磁盘使用分配不均解决办法 Elasticsearch 分页查询三种方案:from/size、search_after、scroll 怎么选→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式