11.2 Mapping 设计最佳实践 Elasticsearch Mapping 设计最佳实践详解 引言 在 Elasticsearch 中,Mapping (映射) 就像数据库中的 Schema,它定义了索引中字段的数据类型、索引方式、分词器等重要属性。一个精心设计的 Mapping 能够极大地提升 Elasticsearch 的性能、搜索效率以及存储效率。反之,糟糕的 Mapping 设计则可能导致查询效率低下、存储空间浪费甚至数据分析结果不准确。因此,Mapping 设计是 Elasticsearch 最佳实践中至关重要的一环。
在 Elasticsearch 中,Mapping (映射) 就像数据库中的 Schema,它定义了索引中字段的数据类型、索引方式、分词器等重要属性。一个精心设计的 Mapping 能够极大地提升 Elasticsearch 的性能、搜索效率以及存储效率。反之,糟糕的 Mapping 设计则可能导致查询效率低下、存储空间浪费甚至数据分析结果不准确。因此,Mapping 设计是 Elasticsearch 最佳实践中至关重要的一环。
本文将深入探讨 Elasticsearch Mapping 设计的最佳实践,结合代码示例和详细解释,帮助您在实际应用中构建高效、可靠的 Elasticsearch 索引。
在深入最佳实践之前,我们先快速回顾一些 Elasticsearch Mapping 的基础概念:
数据类型 (Data Types): 定义字段存储的数据类型,例如 text, keyword, integer, date, boolean 等。选择合适的数据类型是 Mapping 设计的基础。
字段属性 (Field Properties): 每个字段可以设置多种属性,例如:
index: 控制字段是否被索引,以及如何被索引 (true, false, not_analyzed, analyzed).
store: 控制字段值是否被独立存储在 _source 之外,通常用于优化性能,但会增加存储空间。
doc_values: 启用列式存储,用于聚合、排序和脚本操作,默认启用。
norms: 存储字段的标准化因子,用于评分计算,通常文本字段会启用,但 keyword 等字段可以禁用以节省空间。
analyzer: 指定字段使用的分词器,用于文本字段的索引和搜索。
search_analyzer: 指定搜索时使用的分词器,通常与 analyzer 相同,但可以根据需要进行区分。
format: 指定日期类型字段的格式。
fields: 允许为同一个字段定义多字段类型,例如同时拥有 text 和 keyword 类型。
动态 Mapping (Dynamic Mapping): Elasticsearch 默认开启的特性,当索引新文档时,如果遇到新的字段,Elasticsearch 会自动根据字段值推断数据类型并添加到 Mapping 中。
显式 Mapping (Explicit Mapping): 预先定义索引的 Mapping,明确指定每个字段的数据类型和属性。
索引模板 (Index Templates): 允许为匹配特定模式的索引预定义 Mapping 和 Settings,方便批量创建索引并保持 Mapping 的一致性。
以下是一些 Elasticsearch Mapping 设计的最佳实践,我们将结合代码示例进行详细讲解:
最佳实践: 始终优先使用显式 Mapping 而不是依赖动态 Mapping。
原因:
可预测性: 显式 Mapping 确保了字段的数据类型和属性是可预测和可控的,避免了动态 Mapping 自动推断类型可能带来的错误或不符合预期的结果。例如,数字字符串 "123" 可能会被动态 Mapping 推断为 long 类型,但如果需要将其作为 keyword 类型进行精确匹配,则会产生问题。
性能优化: 显式 Mapping 可以根据实际需求选择最合适的数据类型和属性,例如禁用不必要的 norms 或 doc_values,从而优化索引大小和查询性能。
数据一致性: 显式 Mapping 保证了索引中所有文档的字段结构一致,避免了由于动态 Mapping 导致不同文档字段类型不一致的问题。
代码示例 (显式 Mapping):
PUT /my_index { "mappings": { "properties": { "title": { "type": "text", "analyzer": "ik_max_word" }, "content": { "type": "text", "analyzer": "ik_smart" }, "author": { "type": "keyword" }, "publish_date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, "view_count": { "type": "integer" } } } }
代码解释:
以上代码创建了一个名为 my_index 的索引,并定义了显式 Mapping。
properties 字段下定义了每个字段的类型和属性。
title 和 content 字段被定义为 text 类型,并分别使用了 ik_max_word 和 ik_smart 中文分词器。
author 字段被定义为 keyword 类型,用于精确匹配。
publish_date 字段被定义为 date 类型,并指定了日期格式。
view_count 字段被定义为 integer 类型。
动态 Mapping 的风险:
如果依赖动态 Mapping,当索引如下文档时:
POST /my_index/_doc { "title": "Elasticsearch 最佳实践", "content": "Mapping 设计非常重要", "author": 123, // 错误:本应为 keyword 类型,动态 Mapping 可能推断为 long "publish_date": "2023-10-27", "view_count": "abc" // 错误:本应为 integer 类型,动态 Mapping 可能推断为 text }
author 和 view_count 字段的数据类型可能会被动态 Mapping 错误地推断,导致后续查询和分析出现问题。
最佳实践: 根据字段的用途和数据特性,选择最合适的数据类型。
常用数据类型及其适用场景:
| 数据类型 | 适用场景 | 示例 |
|---|---|---|
text |
全文搜索,需要分词的文本内容,例如文章内容、商品描述等。 | "Elasticsearch 权威指南" |
keyword |
精确匹配、排序、聚合的字段,例如标签、分类、ID、状态等。 | "技术", "商品ID-123", "已发布" |
integer |
整数值,例如年龄、数量、计数等。 | 100, 12345 |
long |
长整型值,适用于较大范围的整数,例如时间戳 (毫秒级)。 | 1677676800000L |
float, double |
浮点数,例如价格、评分、地理坐标等。 | 99.99, 3.1415926 |
boolean |
布尔值,例如是否启用、是否成功等。 | true, false |
date |
日期和时间,可以指定日期格式。 | "2023-10-27", "2023-10-27T10:00:00Z" |
geo_point |
地理坐标点,用于地理位置搜索和分析。 | "40.715,-74.011" (经纬度字符串), { "lat": 40.715, "lon": -74.011 } |
nested |
嵌套对象,用于索引数组对象,保持数组中对象的独立性,适用于复杂对象关系建模。 | 例如订单中的商品列表 |
object |
对象,用于索引简单的键值对结构,对象内部字段会被扁平化处理,不保持对象结构的独立性。 | 例如用户信息中的地址信息 |
选择错误数据类型的后果:
text 类型用于 ID 字段: 会进行分词,导致无法进行精确匹配,且浪费存储空间和索引资源。
keyword 类型用于全文搜索字段: 无法进行分词搜索,搜索效果差。
数值类型使用 text 类型: 无法进行数值范围查询、排序和聚合。
日期类型使用 text 类型: 无法进行日期范围查询、排序和聚合,且日期格式解析复杂。
代码示例 (选择合适的数据类型):
PUT /product_index { "mappings": { "properties": { "product_id": { "type": "keyword" // 产品 ID,精确匹配 }, "product_name": { "type": "text", // 产品名称,全文搜索 "analyzer": "ik_max_word" }, "price": { "type": "float" // 产品价格,数值范围查询 }, "category": { "type": "keyword" // 产品分类,聚合分析 }, "tags": { "type": "keyword" // 产品标签,多值字段,精确匹配和聚合 }, "created_at": { "type": "date", // 创建时间,日期范围查询和排序 "format": "yyyy-MM-dd HH:mm:ss" }, "location": { "type": "geo_point" // 产品地理位置,地理位置搜索 } } } }
keyword 和 text 类型最佳实践: 清晰区分 keyword 和 text 类型的用途,并根据需求选择合适的类型。
keyword 类型:
用于精确匹配、排序、聚合。
不进行分词,索引时将字段值作为一个完整的 term 存储。
适用于 ID、枚举值、标签、分类等字段。
性能较高,占用存储空间较小 (通常)。
text 类型:
用于全文搜索。
进行分词,索引时将字段值拆分成多个 term 存储。
适用于文章内容、商品描述、评论等需要进行关键词搜索的字段。
性能相对较低,占用存储空间较大 (通常)。
多字段类型 (fields) 的应用:
当需要同时支持精确匹配和全文搜索时,可以使用多字段类型。为一个字段同时定义 keyword 和 text 两种类型。
代码示例 (多字段类型):
PUT /blog_index { "mappings": { "properties": { "title": { "type": "text", "analyzer": "ik_max_word", "fields": { "keyword": { // 定义 keyword 子字段 "type": "keyword", "ignore_above": 256 // keyword 类型字段长度限制,超过会被忽略,节省空间 } } } } } }
代码解释:
title 字段被定义为 text 类型,用于全文搜索。
在 fields 属性中,定义了一个名为 keyword 的子字段,类型为 keyword。
现在,我们可以使用 title 字段进行全文搜索,使用 title.keyword 字段进行精确匹配、排序和聚合。
查询示例:
全文搜索: GET /blog_index/_search?q=title:最佳实践 (使用 title 字段)
精确匹配: GET /blog_index/_search?q=title.keyword:Elasticsearch最佳实践 (使用 title.keyword 字段)
聚合:
GET /blog_index/_search { "aggs": { "title_terms": { "terms": { "field": "title.keyword" // 使用 title.keyword 进行聚合 } } } }
doc_values 和 norms最佳实践: 根据字段的用途,合理启用或禁用 doc_values 和 norms,以优化性能和存储空间。
doc_values: 默认情况下,大多数字段类型都会启用 doc_values。doc_values 以列式存储字段值,用于聚合、排序和脚本操作。如果字段不需要用于聚合、排序或脚本,可以禁用 doc_values 以节省磁盘空间和索引时间。keyword、integer、long、float、double、boolean、date、geo_point 等类型默认启用 doc_values。text 类型默认禁用 doc_values。
norms: norms 用于存储字段的标准化因子,用于计算文档的相关性评分。对于 text 类型字段,norms 默认启用。如果字段不需要参与评分计算 (例如 keyword 类型、过滤字段),可以禁用 norms 以节省空间。
何时禁用 doc_values 和 norms:
doc_values: 当字段仅用于过滤查询,而不需要用于聚合、排序或脚本时,可以禁用 doc_values。例如,一些状态字段、标识字段等。
norms: 当字段不参与评分计算时,可以禁用 norms。例如,keyword 类型字段、数值类型字段、日期类型字段等。
代码示例 (禁用 doc_values 和 norms):
PUT /log_index { "mappings": { "properties": { "log_level": { "type": "keyword", "doc_values": false, // 禁用 doc_values,log_level 字段仅用于过滤 "norms": false // 禁用 norms,log_level 字段不参与评分 }, "timestamp": { "type": "date", "doc_values": true, // 启用 doc_values,timestamp 字段可能用于时间范围聚合和排序 "norms": false // 禁用 norms,timestamp 字段不参与评分 }, "message": { "type": "text", "analyzer": "ik_smart", "doc_values": false, // text 类型默认禁用 doc_values "norms": true // text 类型默认启用 norms } } } }
注意事项:
禁用 doc_values 或 norms 后,将无法在该字段上执行相应的操作 (聚合、排序、脚本、评分)。
权衡存储空间和功能需求,谨慎禁用。
可以使用 _mapping API 查看索引的 Mapping 信息,确认 doc_values 和 norms 的设置。
最佳实践: 根据语言和搜索需求,选择合适的分词器。
常用分词器:
Standard Analyzer: 默认分词器,基于 Unicode 标准进行分词,适用于英文和西文。对于中文,会将中文单字切分。
Simple Analyzer: 按照非字母字符切分,并转换为小写。
Whitespace Analyzer: 按照空格切分。
Stop Analyzer: 类似于 Simple Analyzer,但会移除停用词 (例如 "the", "a", "is")。
Keyword Analyzer: 不分词,将整个输入作为一个 term。
Pattern Analyzer: 使用正则表达式进行分词。
Language Analyzers: 针对特定语言的分词器,例如 english, french, german, chinese (需要安装插件,例如 ik_analyzer, pinyin_analyzer)。
中文分词器:
ik_analyzer: 常用的中文分词器,支持 ik_max_word (最细粒度分词) 和 ik_smart (智能分词) 两种模式。
pinyin_analyzer: 拼音分词器,可以将中文转换为拼音进行搜索。
hanlp-analyzer: 基于 HanLP 自然语言处理工具包的分词器,功能强大,但配置相对复杂。
选择分词器的原则:
语言: 根据索引的文档语言选择相应的语言分词器。
搜索需求:
精确匹配: keyword 分词器或不分词。
模糊匹配、关键词搜索: text 类型字段,选择合适的语言分词器 (例如英文用 english, 中文用 ik_analyzer)。
拼音搜索: pinyin_analyzer。
性能: 不同的分词器性能有所差异,需要根据实际情况进行测试和选择。
代码示例 (选择分词器):
PUT /news_index { "settings": { "analysis": { "analyzer": { "my_custom_analyzer": { // 自定义分词器 "type": "custom", "tokenizer": "ik_max_word", // 使用 ik_max_word 分词器 "filter": [ "lowercase" ] // 添加 lowercase filter,将词条转换为小写 } } } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "my_custom_analyzer" // 使用自定义分词器 }, "content": { "type": "text", "analyzer": "ik_smart" // 使用 ik_smart 分词器 }, "author": { "type": "keyword" // keyword 类型不需要分词器 } } } }
代码解释:
在 settings.analysis.analyzer 中定义了一个名为 my_custom_analyzer 的自定义分词器。
tokenizer 指定使用 ik_max_word 分词器。
filter 添加了 lowercase filter,将分词结果转换为小写。
title 字段使用了自定义分词器 my_custom_analyzer。
content 字段使用了 ik_smart 分词器。
author 字段是 keyword 类型,不需要指定分词器。
最佳实践: 使用索引模板来管理和统一具有相似 Mapping 结构的索引。
索引模板的作用:
自动化: 当创建符合模板模式的新索引时,Elasticsearch 会自动应用模板中定义的 Settings 和 Mappings。
一致性: 确保同一类型的索引具有相同的 Mapping 结构,避免手动创建索引时出现 Mapping 不一致的问题。
简化管理: 集中管理索引的通用配置,方便维护和更新。
索引模板组成:
index_patterns: 指定模板应用的索引名称模式,可以使用通配符 *。
settings: 索引设置,例如 number_of_shards, number_of_replicas, analysis 等。
mappings: 索引 Mapping 定义。
aliases: 索引别名定义。
Mermaid 图表 (索引模板应用流程):
代码示例 (创建索引模板):
PUT /_index_template/my_template { "index_patterns": ["logstash-*"], // 匹配以 logstash- 开头的索引 "template": { "settings": { "number_of_shards": 3, "number_of_replicas": 1 }, "mappings": { "properties": { "timestamp": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, "message": { "type": "text", "analyzer": "ik_smart" }, "log_level": { "type": "keyword" } } } }, "priority": 1, // 模板优先级,数字越大优先级越高 "version": 1 // 模板版本号 }
代码解释:
创建了一个名为 my_template 的索引模板。
index_patterns 指定模板应用于所有以 logstash- 开头的索引。
template.settings 定义了索引的 Settings,例如分片数和副本数。
template.mappings 定义了索引的 Mapping,包括 timestamp, message, log_level 字段。
priority 设置模板优先级,当多个模板匹配同一个索引时,优先级最高的模板会被应用。
version 设置模板版本号,用于跟踪模板的更新。
应用示例:
当创建索引 logstash-2023-10-27 时,由于索引名称匹配 logstash-* 模式,my_template 模板会被自动应用,索引将使用模板中定义的 Settings 和 Mappings。
最佳实践: 对 Mapping 进行版本控制,方便追踪和回滚 Mapping 的变更。
版本控制方法:
在索引模板中维护版本号 (version 字段): 每次修改模板时,更新 version 字段。
使用外部版本控制系统 (例如 Git) 管理 Mapping 定义文件: 将 Mapping 定义保存为 JSON 文件,并使用 Git 进行版本管理。
记录 Mapping 变更日志: 详细记录每次 Mapping 变更的内容、时间和操作人。
版本控制的好处:
可追溯性: 可以清晰地了解 Mapping 的演变历史。
可回滚性: 当 Mapping 变更导致问题时,可以快速回滚到之前的版本。
协作性: 方便团队成员协作管理 Mapping,避免冲突。
代码示例 (更新索引模板版本):
PUT /_index_template/my_template { "index_patterns": ["logstash-*"], "template": { // ... (settings 和 mappings 定义) ... }, "priority": 1, "version": 2 // 版本号更新为 2 }
注意事项:
Mapping 的修改通常需要重建索引才能生效。对于线上运行的索引,Mapping 修改需要谨慎操作,通常需要采用滚动重启或 Reindex 等策略。
尽量避免频繁修改 Mapping,良好的初始 Mapping 设计可以减少后续的维护成本。
避免过度索引: 只索引需要的字段,避免索引不必要的字段,减少索引大小和提高索引性能。可以使用 _source 字段来存储原始文档,以便在需要时获取完整数据。
合理使用 store 属性: 默认情况下,字段值存储在 _source 字段中。如果需要频繁访问某个字段的值,且不希望从 _source 中解析,可以设置 store: true 将字段值独立存储,但会增加存储空间。
谨慎使用 nested 类型: nested 类型适用于复杂对象关系建模,但会增加索引和查询的开销。如果对象结构不复杂,可以考虑使用 object 类型或将对象扁平化处理。
关注 Mapping 字段数量限制: Elasticsearch 默认限制单个索引的字段数量,避免 Mapping 过于庞大,影响性能。可以通过 index.mapping.total_fields.limit 设置调整字段数量限制。
Mapping 设计是 Elasticsearch 性能优化的关键环节。本文详细介绍了 Elasticsearch Mapping 设计的最佳实践,包括:
显式 Mapping 优先
选择合适的数据类型
合理使用 keyword 和 text 类型
优化 doc_values 和 norms
选择合适的分词器 (Analyzer)
使用索引模板 (Index Templates)
版本控制 Mapping
其他 Mapping 设计建议
遵循这些最佳实践,可以帮助您设计出高效、灵活、可维护的 Elasticsearch Mapping,提升 Elasticsearch 的整体性能和应用效果。在实际应用中,需要根据具体的业务场景和数据特点,灵活运用这些最佳实践,并持续优化 Mapping 设计,以满足不断变化的需求。
希望本文能够帮助您深入理解 Elasticsearch Mapping 设计,并在实际工作中应用这些最佳实践。