Mapping 决定数据如何索引
Mapping 类似搜索索引的数据结构定义,它决定字段类型、是否分词、能否聚合以及采用什么分析器。生产环境建议显式维护 Mapping,避免动态映射把日期、数字或标识符识别成错误类型。
text 与 keyword
text 字段会经过分析器分词,适合标题、正文等全文检索。keyword 保留完整值,适合状态、标签、订单号、排序和聚合。
{
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "standard" },
"status": { "type": "keyword" },
"created_at": { "type": "date" }
}
}
}
同一个字符串可以使用 multi-fields 同时建立 text 和 keyword 子字段,分别服务搜索与聚合,但会增加索引体积。
分析器链路
分析器通常包含字符过滤器、分词器和词元过滤器。写入时的分析方式应与查询意图匹配。中文检索通常需要选择合适的中文分词方案,并维护词典、同义词和版本发布流程。
使用 _analyze API 可以查看文本最终生成哪些词项,这是排查“明明有数据却搜不到”的第一步。
match 与 term
match 会分析查询文本,适合全文字段;term 不分析输入,要求精确匹配索引中的词项,适合 keyword、数字、布尔等字段。
对 text 字段直接使用 term 查询原始句子通常得不到预期结果,因为原文已经被拆成多个词项。反过来,对订单号使用 match 也可能产生意外分词。
bool 查询
must:必须匹配并参与相关性评分。filter:必须匹配但不参与评分,适合状态和时间范围。should:提高匹配文档得分,可配置最少匹配数量。must_not:排除文档。
不需要相关性评分的条件应放入 filter 上下文,语义更准确,也更有利于缓存和执行优化。
Mapping 变更策略
许多字段类型和分析器不能在已有索引上直接修改。标准流程是创建带新 Mapping 的版本化索引,使用 _reindex 或业务双写迁移数据,验证后原子切换别名,最后再清理旧索引。
常见问题
- 字段爆炸:动态键生成海量字段,应限制动态映射或改用扁平结构。
- 深分页:
from + size越深成本越高,大结果遍历使用search_after配合稳定排序。 - 聚合内存高:避免直接对超高基数字段做巨型聚合。
- Mapping 不一致:索引模板应纳入版本管理,并在写入前创建索引。
核心考点清单
- Mapping 决定字段如何索引,生产环境应显式管理并纳入版本控制。
- text 分词用于全文搜索,keyword 保留完整值用于过滤、排序和聚合。
- match 会分析查询文本,term 直接匹配词项。
- 不可兼容 Mapping 变更通常通过新索引、Reindex 和别名切换完成。
高频追问与参考回答
追问:为什么不能直接修改字段类型?
已有倒排和列式数据按旧类型编码,直接改变解释会破坏索引。应创建新版本索引并迁移,验证后原子切换别名。
追问:深分页为什么慢?
每个分片都要取出并排序 from + size 条候选,再由协调节点合并。持续遍历应使用带稳定排序的 search_after。
机制全景图
下面把「Elasticsearch Mapping、分词与查询选择」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。
flowchart LR
A["识别字段业务语义"]
A --> B["定义 Mapping 与 Analyzer"]
B --> C["写入并建立索引结构"]
C --> D["选择 Query/Filter 上下文"]
D --> E["聚合排序并返回"]
完整链路:从输入到结果
沿着「识别字段业务语义 → 定义 Mapping 与 Analyzer → 写入并建立索引结构 → 选择 Query/Filter 上下文 → 聚合排序并返回」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。
1. 识别字段业务语义
字段类型决定可用查询、存储格式和内存结构,动态映射方便但可能把 ID、日期或数值识别错误。
2. 定义 Mapping 与 Analyzer
text/keyword、多字段、normalizer 和 nested 等设计应来自查询模式而不是样例 JSON。
3. 写入并建立索引结构
写入后字段值分别进入倒排、doc values 和 _source;关闭某能力会影响搜索、聚合或回显。
4. 选择 Query/Filter 上下文
Query Context 计算相关性,Filter Context 只判断匹配并更易缓存,精确条件不应无谓评分。
5. 聚合排序并返回
聚合与排序通常依赖 doc values,高基数字段和大 size 会显著增加内存与跨分片合并。
源码与实现定位
| 入口 | 阅读重点 |
|---|---|
| MapperService | Mapping 合并与字段类型 |
| _field_caps | 跨索引字段能力 |
源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。
参数配置与可复现实验
PUT _index_template/orders
{"template":{"mappings":{"dynamic":"strict","properties":{"order_id":{"type":"keyword"}}}}}
写入数字、前导零、数组对象和未知字段,验证 strict 模板与 nested 语义。
验证步骤与预期结果
1. 固定输入和基线
先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「mapping 冲突」为主基线,记录值应满足「记录稳态基线」;同时保存 字段总数、mapping 冲突数,使后续变化能够回到同一时间轴比较。
2. 从实现入口确认路径
在「MapperService」确认请求确实进入「Mapping 合并与字段类型」对应的实现,再沿「_field_caps」观察「跨索引字段能力」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。
3. 注入本文特有的失败模式
优先复现「字段爆炸触发 mapping limit」,并把单一变量逐级放大,直到「mapping 冲突」越过「超过基线2倍」。随后再分别验证「数组对象误用 object 导致跨对象匹配」和「在 filter 条件使用 script 扫描每文档」,三类故障分开执行,避免多个变量同时变化而无法归因。
4. 执行止损和根因修复
第一轮只应用「核心字段显式 Mapping」,确认它能控制影响范围;第二轮应用「限制 total_fields」,验证核心链路恢复;最后落实「类型变化新索引+alias」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。
5. 通过退出条件
实验只有同时满足三项才算通过:「mapping 冲突」回到「记录稳态基线」、「P99」回到「小于业务预算」、「结果差异」回到「0」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。
量化基线
| 指标 | 样例基线/口径 | 风险线 | 结论 |
|---|---|---|---|
| mapping 冲突 | 记录稳态基线 | 超过基线2倍 | 按实现入口定位 |
| P99 | 小于业务预算 | 突破预算 | 停止扩量 |
| 结果差异 | 0 | 任意非零 | 回滚并重建 |
这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。
事故复盘:订单 ID 自动映射成 long 后前导零丢失
测试样本全是数字,动态映射把订单号识别为 long,后续带字母数据写入失败,精确格式也无法保留。通过索引模板显式定义 keyword 并版本化 alias 后,数据契约才稳定。
| 失败模式 | 首要证据 | 第一处置动作 |
|---|---|---|
| 字段爆炸触发 mapping limit | 字段总数 | 核心字段显式 Mapping |
| 数组对象误用 object 导致跨对象匹配 | mapping 冲突数 | 限制 total_fields |
| 在 filter 条件使用 script 扫描每文档 | 查询 cache 命中 | 类型变化新索引+alias |
发布与回滚检查点
- 发布前:确认「MapperService」对应实现和上述配置在目标版本仍然有效,并保存「mapping 冲突」基线。
- 灰度中:同时观察 字段总数、mapping 冲突数、查询 cache 命中;任一指标越过表中风险线,就停止继续扩量。
- 回滚时:先执行「核心字段显式 Mapping」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
- 发布后:至少覆盖一个完整峰值周期,确认「字段爆炸触发 mapping limit」没有再次出现,才关闭变更观察窗口。
方案对比与选型
| 方案 | 更适合的场景 | 主要收益 | 代价与边界 |
|---|---|---|---|
| 显式 Mapping | 生产核心索引 | 类型可控、错误前置 | 演进需模板与重建 |
| 动态模板 | 字段模式可预测的大量日志 | 减少手工定义 | 规则优先级错误会污染索引 |
| runtime field | 临时派生或历史兼容 | 无需立即重建 | 查询时计算成本高 |
选型至少带上 文档规模、分片数、字段基数、查询并发和写入速率,并用上面的量化基线验证;未知数据应明确为待测假设。
设计边界与工程取舍
Mapping 是写入前的物理设计,许多字段类型不能原地改变;重要索引应通过模板、版本索引和 alias 切换演进。
工程落地遵循:先设计 Mapping 与分片,再优化查询;任何调优都要控制扫描与内存放大。回答时直接引用「MapperService」、配置实验和事故数据,比复述固定模板更有说服力。