4. 索引建模与Mapping设计
索引建模是 Elasticsearch 工程化落地中最容易被低估、却最难返工的一环。Mapping 一旦写入并产生数据,字段类型基本不可修改,只能通过重建索引迁移。因此,建模阶段的核心任务不是“把数据塞进去”,而是提前回答三个问题:这个字段要支持什么样的查询?它需要被排序或聚合吗?它的写入量级和保留周期是多少?本节围绕字段类型、分词器、多字段、动态模板、索引生命周期与建模原则展开,并给出可直接参考的配置示例。
一、字段类型:先定查询语义,再定类型
Elasticsearch 的字段类型决定了倒排索引的构建方式与可用的查询能力。常见类型可分为四类:文本类(text、keyword)、数值与日期类(long、integer、double、scaled_float、date)、结构化类(boolean、ip、geo_point、nested、object)以及专用类(dense_vector、rank_feature、alias)。
最关键的区分是 text 与 keyword。text 会经过分词器切分成词项,适合全文检索,但默认不能直接用于排序和聚合;keyword 保留完整原值,适合精确匹配、排序、聚合与脚本字段。一个典型误区是把商品名称、日志消息这类需要全文检索的字段设为 keyword,结果只能做精确匹配;反过来把订单号、状态码设为 text,则聚合结果会被拆得支离破碎。
数值类型的选择同样影响存储与精度。金额字段若使用 double,浮点误差可能在聚合时累积;推荐使用 scaled_float 并指定 scaling_factor,例如金额以“分”为单位存储为 long,或使用 scaled_float 配合 scaling_factor: 100。日期字段应显式指定 format,避免不同来源的时间格式导致解析失败。
二、分词器:控制文本如何被切分
分词器由三部分组成:字符过滤器(character filter)、分词器(tokenizer)、词项过滤器(token filter)。中文场景下,标准分词器会把连续汉字切成单字,检索召回高但精度低;常用方案是 IK 分词器的 ik_max_word(写入时细粒度)与 ik_smart(查询时粗粒度)组合,或使用内置的 smartcn。
分词器需要在索引设置中定义,并在 Mapping 中引用。下面是一个可参考的示例:
PUT /product_v1
{
"settings": {
"analysis": {
"analyzer": {
"product_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase", "asciifolding"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "product_analyzer",
"search_analyzer": "ik_smart"
},
"brand": { "type": "keyword" },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||epoch_millis" }
}
}
}
写入时用细粒度分词提高召回,查询时用粗粒度分词减少噪声,这是中文检索中常见的组合策略。需要注意的是,search_analyzer 只影响查询阶段,不影响已写入的倒排索引。
三、多字段:一个字段,多种用途
多字段(multi-fields)允许同一原始值以不同方式索引。最典型的用法是 text 主字段加 keyword 子字段:
"title": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"raw": { "type": "keyword", "ignore_above": 256 }
}
}
这样 title 用于全文检索,title.raw 用于排序、聚合和精确匹配。ignore_above 防止超长字符串进入 keyword 索引造成内存膨胀。多字段会带来额外的索引开销,应只对确实需要多种访问方式的字段使用。
四、动态模板:让新字段自动落入正确类型
当数据源字段不固定时,动态映射(dynamic mapping)可能把数字字符串识别成 text,或把日期识别成 text。动态模板(dynamic templates)通过匹配规则为新字段指定类型:
"dynamic_templates": [
{
"strings_as_keywords": {
"match_mapping_type": "string",
"match": "code_*",
"mapping": { "type": "keyword" }
}
},
{
"longs_as_integers": {
"match_mapping_type": "long",
"mapping": { "type": "integer" }
}
}
]
动态模板按顺序匹配,先命中者生效。生产环境通常将 dynamic 设为 strict 或 false,避免未知字段污染 Mapping;strict 会在写入未知字段时直接报错,适合结构稳定的核心索引。
五、索引生命周期与建模原则
索引生命周期管理(ILM)把索引按时间划分为 hot、warm、cold、delete 四个阶段。日志类场景常见策略是:hot 阶段 7 天、warm 阶段 30 天并强制合并段、cold 阶段 90 天、之后删除。ILM 依赖 rollover 别名,写入指向别名而非具体索引,当索引达到 max_size 或 max_age 时自动滚动到新索引。
建模原则可以归纳为几条:按查询模式设计,而非按数据源结构照搬;能用 keyword 精确匹配的不要用 text;需要聚合的字段必须是 doc_values 可用的类型;避免过度 nested,nested 查询代价高,能用扁平结构加过滤解决的优先扁平化;控制单索引分片数,分片过多会放大集群元数据与查询开销,一般单分片 10~50GB 为宜。
_analyze API 验证分词结果是否符合预期。上图把建模过程串成一条决策链:先判断字段语义,再选类型与分词器,必要时用多字段兼顾检索与聚合,用动态模板兜住未知字段,最后用 ILM 管理索引的时间维度。实际落地时,建议把这条链固化为团队评审清单,在索引创建前逐项确认,避免上线后因 Mapping 不可变而被迫重建索引。
_reindex 迁移数据并切换别名。因此,任何涉及字段类型变更的需求,都应在建模评审阶段拦截。