11.2 Mapping 设计最佳实践


文档摘要

11.2 Mapping 设计最佳实践 Elasticsearch Mapping 设计最佳实践详解 引言 在 Elasticsearch 中,Mapping (映射) 就像数据库中的 Schema,它定义了索引中字段的数据类型、索引方式、分词器等重要属性。一个精心设计的 Mapping 能够极大地提升 Elasticsearch 的性能、搜索效率以及存储效率。反之,糟糕的 Mapping 设计则可能导致查询效率低下、存储空间浪费甚至数据分析结果不准确。因此,Mapping 设计是 Elasticsearch 最佳实践中至关重要的一环。

11.2 Mapping 设计最佳实践

Elasticsearch Mapping 设计最佳实践详解

1. 引言

在 Elasticsearch 中,Mapping (映射) 就像数据库中的 Schema,它定义了索引中字段的数据类型、索引方式、分词器等重要属性。一个精心设计的 Mapping 能够极大地提升 Elasticsearch 的性能、搜索效率以及存储效率。反之,糟糕的 Mapping 设计则可能导致查询效率低下、存储空间浪费甚至数据分析结果不准确。因此,Mapping 设计是 Elasticsearch 最佳实践中至关重要的一环。

本文将深入探讨 Elasticsearch Mapping 设计的最佳实践,结合代码示例和详细解释,帮助您在实际应用中构建高效、可靠的 Elasticsearch 索引。

2. Elasticsearch Mapping 基础概念回顾

在深入最佳实践之前,我们先快速回顾一些 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: 允许为同一个字段定义多字段类型,例如同时拥有 textkeyword 类型。

  • 动态 Mapping (Dynamic Mapping): Elasticsearch 默认开启的特性,当索引新文档时,如果遇到新的字段,Elasticsearch 会自动根据字段值推断数据类型并添加到 Mapping 中。

  • 显式 Mapping (Explicit Mapping): 预先定义索引的 Mapping,明确指定每个字段的数据类型和属性。

  • 索引模板 (Index Templates): 允许为匹配特定模式的索引预定义 Mapping 和 Settings,方便批量创建索引并保持 Mapping 的一致性。

3. Mapping 设计最佳实践

以下是一些 Elasticsearch Mapping 设计的最佳实践,我们将结合代码示例进行详细讲解:

3.1 显式 Mapping 优先

最佳实践: 始终优先使用显式 Mapping 而不是依赖动态 Mapping。

原因:

  • 可预测性: 显式 Mapping 确保了字段的数据类型和属性是可预测和可控的,避免了动态 Mapping 自动推断类型可能带来的错误或不符合预期的结果。例如,数字字符串 "123" 可能会被动态 Mapping 推断为 long 类型,但如果需要将其作为 keyword 类型进行精确匹配,则会产生问题。

  • 性能优化: 显式 Mapping 可以根据实际需求选择最合适的数据类型和属性,例如禁用不必要的 normsdoc_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 字段下定义了每个字段的类型和属性。

  • titlecontent 字段被定义为 text 类型,并分别使用了 ik_max_wordik_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 }

authorview_count 字段的数据类型可能会被动态 Mapping 错误地推断,导致后续查询和分析出现问题。

3.2 选择合适的数据类型

最佳实践: 根据字段的用途和数据特性,选择最合适的数据类型。

常用数据类型及其适用场景:

数据类型 适用场景 示例
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" // 产品地理位置,地理位置搜索 } } } }

3.3 合理使用 keywordtext 类型

最佳实践: 清晰区分 keywordtext 类型的用途,并根据需求选择合适的类型。

keyword 类型:

  • 用于精确匹配、排序、聚合。

  • 不进行分词,索引时将字段值作为一个完整的 term 存储。

  • 适用于 ID、枚举值、标签、分类等字段。

  • 性能较高,占用存储空间较小 (通常)。

text 类型:

  • 用于全文搜索。

  • 进行分词,索引时将字段值拆分成多个 term 存储。

  • 适用于文章内容、商品描述、评论等需要进行关键词搜索的字段。

  • 性能相对较低,占用存储空间较大 (通常)。

多字段类型 (fields) 的应用:

当需要同时支持精确匹配和全文搜索时,可以使用多字段类型。为一个字段同时定义 keywordtext 两种类型。

代码示例 (多字段类型):

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 进行聚合 } } } }

3.4 优化 doc_valuesnorms

最佳实践: 根据字段的用途,合理启用或禁用 doc_valuesnorms,以优化性能和存储空间。

  • doc_values: 默认情况下,大多数字段类型都会启用 doc_valuesdoc_values 以列式存储字段值,用于聚合、排序和脚本操作。如果字段不需要用于聚合、排序或脚本,可以禁用 doc_values 以节省磁盘空间和索引时间。keywordintegerlongfloatdoublebooleandategeo_point 等类型默认启用 doc_valuestext 类型默认禁用 doc_values

  • norms: norms 用于存储字段的标准化因子,用于计算文档的相关性评分。对于 text 类型字段,norms 默认启用。如果字段不需要参与评分计算 (例如 keyword 类型、过滤字段),可以禁用 norms 以节省空间。

何时禁用 doc_valuesnorms:

  • doc_values: 当字段仅用于过滤查询,而不需要用于聚合、排序或脚本时,可以禁用 doc_values。例如,一些状态字段、标识字段等。

  • norms: 当字段不参与评分计算时,可以禁用 norms。例如,keyword 类型字段、数值类型字段、日期类型字段等。

代码示例 (禁用 doc_valuesnorms):

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_valuesnorms 后,将无法在该字段上执行相应的操作 (聚合、排序、脚本、评分)。

  • 权衡存储空间和功能需求,谨慎禁用。

  • 可以使用 _mapping API 查看索引的 Mapping 信息,确认 doc_valuesnorms 的设置。

3.5 选择合适的分词器 (Analyzer)

最佳实践: 根据语言和搜索需求,选择合适的分词器。

常用分词器:

  • 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 类型,不需要指定分词器。

3.6 使用索引模板 (Index Templates)

最佳实践: 使用索引模板来管理和统一具有相似 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。

3.7 版本控制 Mapping

最佳实践: 对 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 设计可以减少后续的维护成本。

3.8 其他 Mapping 设计建议

  • 避免过度索引: 只索引需要的字段,避免索引不必要的字段,减少索引大小和提高索引性能。可以使用 _source 字段来存储原始文档,以便在需要时获取完整数据。

  • 合理使用 store 属性: 默认情况下,字段值存储在 _source 字段中。如果需要频繁访问某个字段的值,且不希望从 _source 中解析,可以设置 store: true 将字段值独立存储,但会增加存储空间。

  • 谨慎使用 nested 类型: nested 类型适用于复杂对象关系建模,但会增加索引和查询的开销。如果对象结构不复杂,可以考虑使用 object 类型或将对象扁平化处理。

  • 关注 Mapping 字段数量限制: Elasticsearch 默认限制单个索引的字段数量,避免 Mapping 过于庞大,影响性能。可以通过 index.mapping.total_fields.limit 设置调整字段数量限制。

4. 总结

Mapping 设计是 Elasticsearch 性能优化的关键环节。本文详细介绍了 Elasticsearch Mapping 设计的最佳实践,包括:

  • 显式 Mapping 优先

  • 选择合适的数据类型

  • 合理使用 keywordtext 类型

  • 优化 doc_valuesnorms

  • 选择合适的分词器 (Analyzer)

  • 使用索引模板 (Index Templates)

  • 版本控制 Mapping

  • 其他 Mapping 设计建议

遵循这些最佳实践,可以帮助您设计出高效、灵活、可维护的 Elasticsearch Mapping,提升 Elasticsearch 的整体性能和应用效果。在实际应用中,需要根据具体的业务场景和数据特点,灵活运用这些最佳实践,并持续优化 Mapping 设计,以满足不断变化的需求。

希望本文能够帮助您深入理解 Elasticsearch Mapping 设计,并在实际工作中应用这些最佳实践。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U