ES 32 - Elasticsearch 数据建模的探索与实践

目录
  • 1 什么是数据建模?

  • 2 如何对 ES 中的数据进行建模

    • 2.1 字段类型的建模方案

    • 2.2 检索、聚合及排序的建模方案

    • 2.3 额外存储的建模方案

  • 3 ES 数据建模实例演示

    • 3.1 动态创建映射关系

    • 3.2 手动创建映射关系

    • 3.3 新增需求 - 添加大字段

    • 3.4 解决大字段带来的性能问题

    • 3.5 mapping中字段的常用参数

    • 3.6 mapping 设置小结

  • 4 ES 数据建模最佳实践

    • 4.1 如何处理关联关系

    • 4.2 避免太多的字段

    • 4.3 避免正则查询

    • 4.4 避免空值引起的聚合不准

  • 参考资料

  • 版权声明

1 什么是数据建模?

数据建模(Data modeling), 是创建数据模型的过程.

数据模型是对真实世界进行抽象描述的一种工具和方法, 实现对现实世界的映射. 比如影视作品、演员、观众评论...

数据建模有三个过程: 概念模型 => 逻辑模型 => 数据模型(第三范式)

数据模型, 需要结合使用的数据库类型, 在满足业务读写性能等需求的前提下, 制定出最终的定义.

2 如何对 ES 中的数据进行建模

ES中的数据建模:

由数据存储、检索等功能需求提炼出实体属性、实体之间的关系 =》形成逻辑模型;

由性能需求提炼制定索引模板、索引Mapping(包括字段的配置、关系的处理) ==》形成物理模型.

ES 中存储、检索的基本单位是索引文档(document), 文档由字段(field)组成, 所以ES的建模就是对字段进行建模.

文档类似于关系型数据库中的一行数据, 字段对应关系型数据库中的某一列数据.

2.1 字段类型的建模方案

(1) text 与 keyword 比较:

  • text: 用于全文本字段, 文本会被 Analyzer 分词; 默认不支持聚合分析及排序, 设置 "fielddata": true 即可支持;

  • keyword: 用于 id、枚举及不需要分词的文本, 比如身份证号码、电话号码,Email地址等; 适用于 Filter(精确匹配过滤)、Sorting(排序) 和 Aggregations(聚合).

  • 设置多字段类型:

    默认会为文本类型设置成 text, 并设置一个 keyword 的子字段;
    在处理人类自然语⾔时, 可以添加“英⽂”、“拼⾳”、“标准”等分词器, 提高搜索结果的正确性.

(2) 结构化数据:

  • 数值类型: 尽量选择贴近的类型, 例如可以用 byte, 就不要用 long;

  • 枚举类型: 设置为 keyword, 即使是数字, 也应该设置成 keyword, 获取更好的性能; 另外范围检索使用keyword, 速度更快;

  • 其他类型: 日期、二进制、布尔、地理信息等类型.

2.2 检索、聚合及排序的建模方案

  • 如不需要检索、排序和聚合分析, 则可设置 "enable": false ;

  • 如不需要检索, 则可设置 "index": false ;

  • 如不需要排序、聚合分析功能, 则可设置 "doc_values": false / "fielddate": false ;

  • 更新频繁、聚合查询频繁的 keyword 类型的字段, 推荐设置 "eager_global_ordinals": true .

2.3 额外存储的建模方案

  • 是否需要专门存储当前字段数据?

"store": true, 可以存储该字段的原始内容;

一般结合 "_source": { "enabled": false } 进行使用, 因为默认的 "_source": { "enabled": true } , 也就是添加索引时文档的原始 JSON 结构都会存储到 _source 中.

  • disable_source: 禁用 _source 元字段, 能节约磁盘, 适用于指标型数据 —— 类似于标识字段、时间字段的数据, 不会更新、高亮查询, 多用来进行过滤操作以快速筛选出更小的结果集, 用来支撑更快的聚合操作.

官方建议: 如果更多关注磁盘空间, 那么建议优先考虑增加数据的压缩⽐, 而不是禁用 _source;

无法看到 _source 字段, 就不能做 reindexupdateupdate_by_query 操作;

目前为止, Kibana 中无法对禁用了 _source 字段的索引进行 Discover 挖掘操作.

—— 谨慎禁用 _source 字段, 参考: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html

3 ES 数据建模实例演示

3.1 动态创建映射关系

# 直接写入一本图书信息:POST books/_doc{  "title": "Thinking in Elasticsearch 7.2.0",  "author": "Heal Chow",  "publish_date": "2019-10-01",  "description": "Master the searching, indexing, and aggregation features in Elasticsearch.",  "cover_url": "https://healchow.com/images/29dMkliO2a1f.jpg"}# 查看自动创建的mapping关系:GET books/_mapping# 内容如下:{  "books" : {    "mappings" : {      "properties" : {        "author" : {          "type" : "text",          "fields" : {            "keyword" : {              "type" : "keyword",              "ignore_above" : 256            }          }        },        "cover_url" : {          "type" : "text",          "fields" : {            "keyword" : {              "type" : "keyword",              "ignore_above" : 256            }          }        },        "description" : {          "type" : "text",          "fields" : {            "keyword" : {              "type" : "keyword",              "ignore_above" : 256            }          }        },        "publish_date" : {          "type" : "date"        },        "title" : {          "type" : "text",          "fields" : {            "keyword" : {              "type" : "keyword",              "ignore_above" : 256            }          }        }      }    }  }}

3.2 手动创建映射关系

# 删除自动创建的图书索引:DELETE books# 手动优化字段的mapping:PUT books{  "mappings": {    "_source": { "enabled": true },    "properties": {      "title": {        "type": "text",        "fields": {          "keyword": {            "type": "keyword",            "ignore_above": 100          }        }      },      "author": { "type": "keyword" },      "publish_date": {        "type": "date",        "format": "yyyy-MM-dd HH:mm:ss||yyyyMMddHHmmss||yyyy-MM-dd||epoch_millis"      },      "description": { "type": "text" },      "cover_url": {          # index 设置成 false, 不支持搜索, 但支持 Terms 聚合        "type": "keyword",        "index": false      }    }  }}

说明: _source 元字段默认是开启的, 若禁用后, 就无法对搜索的结果进行展示, 也无法进行 reindexupdateupdate_by_query 操作.

3.3 新增需求 - 添加大字段

  • 需求描述: 添加图书内容字段, 要求支持全文搜索, 并且能够高亮显示.

  • 需求分析: 新需求会导致 _source 的内容过⼤, 虽然我们可以通过source filtering对要搜索结果中的字段进行过滤:

    "_source": {    "includes": ["title"]  # 或 "excludes": ["xxx"] 排除某些字段, includes 优先级更高}

    但这种方式只是 ES 服务端传输给客户端时的过滤, 内部 Fetch 数据时, ES 各数据节点还是会传输 _source 中的所有数据到协调节点 —— 网络 IO 没有得到本质上的降低.

3.4 解决大字段带来的性能问题

(1) 在创建 mapping 时手动关闭 _source 元字段: "_source": { "enabled": false} ;

(2) 然后为每个字段设置 "store": true .

# 关闭_source元字段, 设置store=true:PUT books{  "mappings": {    "_source": { "enabled": false },    "properties": {      "title": {        "type": "text",        "store": true,        "fields": {          "keyword": {            "type": "keyword",            "ignore_above": 100          }        }      },      "author": { "type": "keyword", "store": true },      "publish_date": {        "type": "date",        "store": true,        "format": "yyyy-MM-dd HH:mm:ss||yyyyMMddHHmmss||yyyy-MM-dd||epoch_millis"      },      "description": { "type": "text", "store": true },      "cover_url": {        "type": "keyword",        "index": false,        "store": true      },      "content": { "type": "text", "store": true }    }  }}

(3) 加数据, 并进行高亮查询:

# 添加包含新字段的文档:POST books/_doc{  "title": "Thinking in Elasticsearch 7.2.0",  "author": "Heal Chow",  "publish_date": "2019-10-01",  "description": "Master the searching, indexing, and aggregation features in Elasticsearch.",  "cover_url": "https://healchow.com/images/29dMkliO2a1f.jpg",  "content": "1. Revisiting Elasticsearch and the Changes. 2. The Improved Query DSL. 3. Beyond Full Text Search. 4. Data Modeling and Analytics. 5. Improving the User Search Experience. 6. The Index Distribution Architecture.  .........."}# 通过 stored_fields 指定要查询的字段:GET books/_search{  "stored_fields": ["title", "author", "publish_date"],  "query": {    "match": { "content": "data modeling" }  },  "highlight": {    "fields": { "content": {} }  }}

查询结果如下:

{  "took" : 1,  "timed_out" : false,  "_shards" : {    "total" : 1,    "successful" : 1,    "skipped" : 0,    "failed" : 0  },  "hits" : {    "total" : {      "value" : 1,      "relation" : "eq"    },    "max_score" : 0.5753642,    "hits" : [      {        "_index" : "books",        "_type" : "_doc",        "_id" : "dukLoG0BdfGBNhbF13CJ",        "_score" : 0.5753642,        "highlight" : {          "content" : [            "<em>Data</em> <em>Modeling</em> and Analytics. 5. Improving the User Search Experience. 6."          ]        }      }    ]  }}

(4) 结果说明:

  • 返回结果中不包含 _source 字段;

  • 对需要显示的信息, 要在查询中指定 "stored_fields": ["xxx", "yyy"] ;

  • 禁⽌ _source 字段后, 仍然支持使用 Highlights API 的使用.

3.5 mapping中字段的常用参数

参考: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html

  • enabled – 设置成 false, 当前字段就只存储, 不支持搜索和聚合分析 (数据保存在 _source 中);

  • index – 是否构建倒排索引, 设置成 false, 就无法被搜索, 但还是支持聚合操作, 并会出现在 _source 中;

  • norms – 只⽤来过滤和聚合分析(指标数据)、不关心评分的字段, 建议关闭, 节约存储空间;

  • doc_values – 是否启用 doc_values, 用于排序和聚合分析;

  • field_data – 如果要对 text 类型启用排序和聚合分析, fielddata 需要设置成true;

  • coerce – 是否开启数据类型的自动转换 (如: 字符串转数字), 默认开启;

  • multifields - 是否开启多字段特性;

  • dynamic – 控制 mapping 的动态更新策略, 有 true / false / strict 三种.

doc_values 与 fielddata 比较:

doc_values: 聚合和排序的字段需要开启 —— 默认 为所有非text类型的字段 开启 —— 内存不够时, 会写入磁盘文件中;

fielddata: 是否为text类型开启, 以实现排序和聚合分析 —— 默认关闭 —— 全部加载进内存中.

3.6 mapping 设置小结

(1) 支持加入新的字段 (包括子字段)、更换分词器等操作:

可以通过 update_by_query 令旧数据得到清洗.

(2) Index Template: 根据索引的名称匹配不同的 mappings 和 settings;

(3) Dynamic Template: 在一个 mapping 上动态设定字段类型;

(4) Reindex: 如果要修改、删除已经存在的字段, 或者修改分片个数等参数, 就要重建索引.

必须停机, 数据量大时耗时会比较久.

可借助 Index Alias (索引别名) 来实现零停机维护.

4 ES 数据建模最佳实践

4.1 如何处理关联关系

(1) 范式化设计:

我们知道, 在关系型数据库中有“范式化设计”的概念, 有 1NF、2NF、3NF、BCNF 等等, 主要目标是减少不必要的更新, 虽然节省了存储空间, 但缺点是数据读取操作可能会更慢, 尤其是跨表操作, 需要 join 的表会很多.

反范式化设计: 数据扁平, 不使用关联关系, 而是在文档中通过 _source 字段来保存冗余的数据拷贝.

优点: 无需处理 join 操作, 数据读取性能好;

缺点: 不适合数据频繁修改的场景.

==》ES 不擅长处理关联关系, 一般可以通过对象类型(object)、嵌套类型(nested)、父子关联关系(child/parent)解决.

具体使用所占篇幅较大, 这里省略.

4.2 避免太多的字段

(1) 一个⽂档中, 最好不要有⼤量的字段:

  • 过多的字段导致数据不容易维护;

  • mapping 信息保存在 Cluster State 中, 数据量过⼤, 对集群性能会有影响 (Cluster State 信息需要和所有的节点同步);

  • 删除或修改字段时, 需要 reindex;

(2) ES中单个索引最大字段数默认是 1000, 可以通过参数 index.mapping.total_fields.limt 修改最⼤字段数.

思考: 什么原因会导致文档中有成百上千的字段?

ES 是无模式 (schemaless) 的, 默认情况下, 每添加一个字段, ES 都会根据该字段可能的类型自动添加映射关系.

如果业务处理不严谨, 会出现字段爆炸的现象. 为了避免这种现象的发生, 需要制定 dynamic 策略:

  • true - 未知字段会被自动加入, 是默认设置;

  • false - 新字段不会被索引, 但是会保存到 _source 中;

  • strict - 新增字段不会被索引, ⽂档写入失败, 抛出异常.

—— 生产环境中, 尽量不要使用默认的 "dynamic": true .

4.3 避免正则查询

正则、前缀、通配符查询, 都属于 Term 查询, 但是性能很不好(扫描所有文档, 并逐一比对), 特别是将通配符放在开头, 会导致性能灾难.

(1) 案例:

  • 文档中某个字段包含了 Elasticsearch 的版本信息, 例如 version: "7.2.0" ;

  • 搜索某系列的 bug_fix 版本(末位非0的版本号)? 每个主要版本号所关联的文档?

(2) 通配符查询示例:

# 插入2条数据:PUT softwares/_doc/1{  "version": "7.2.0",  "doc_url": "https://www.elastic.co/guide/en/elasticsearch/.../.html"}PUT softwares/_doc/2{  "version": "7.3.0",  "doc_url": "https://www.elastic.co/guide/en/elasticsearch/.../.html"}# 通配符查询:GET softwares/_search{  "query": {    "wildcard": {      "version": "7*"    }  }}

(3) 解决方案 - 将字符串类型转换为对象类型:

# 创建对象类型的映射:PUT softwares{  "mappings": {    "properties": {      "version": {# 版本号设置为对象类型        "properties": {          "display_name": { "type": "keyword" },          "major": { "type": "byte" },          "minor": { "type": "byte" },          "bug_fix": { "type": "byte" }        }      },      "doc_url": { "type": "text" }    }  }}# 添加数据:PUT softwares/_doc/1{  "version": {    "display_name": "7.2.0",    "major": 7,    "minor": 2,    "bug_fix": 0  },  "doc_url": "https://www.elastic.co/guide/en/elasticsearch/.../.html"}PUT softwares/_doc/2{  "version": {    "display_name": "7.3.0",    "major": 7,    "minor": 3,    "bug_fix": 0  },  "doc_url": "https://www.elastic.co/guide/en/elasticsearch/.../.html"}# 通过filter过滤, 避免正则查询, 大大提升性能:GET softwares/_search{  "query": {    "bool": {      "filter": [        {          "match": { "version.major": 7 }        },        {          "match": { "version.minor": 2 }        }      ]    }  }}

4.4 避免空值引起的聚合不准

(1) 示例:

# 添加数据, 包含1条 null 值的数据:PUT ratings/_doc/1{  "rating": 5}PUT ratings/_doc/2{  "rating": null}# 对含有 null 值的字段进行聚合:GET ratings/_search{  "size": 0,  "aggs": {    "avg_rating": {      "avg": { "field": "rating"}    }  }}# 结果如下:{  "took" : 3,  "timed_out" : false,  "_shards" : {    "total" : 1,    "successful" : 1,    "skipped" : 0,    "failed" : 0  },  "hits" : {    "total" : {      "value" : 2,# 2条数据, avg_rating 结果不正确      "relation" : "eq"    },    "max_score" : null,    "hits" : [ ]  },  "aggregations" : {    "avg_rating" : {      "value" : 5.0    }  }}

(2) 使用 null_value 解决空值的问题:

# 创建 mapping 时, 设置 null_value:PUT ratings{  "mappings": {    "properties": {      "rating": {        "type": "float",        "null_value": "1.0"      }    }  }}# 添加相同的数据, 再次聚合, 结果正确:{  "took" : 0,  "timed_out" : false,  "_shards" : {    "total" : 1,    "successful" : 1,    "skipped" : 0,    "failed" : 0  },  "hits" : {    "total" : {      "value" : 2,      "relation" : "eq"    },    "max_score" : null,    "hits" : [ ]  },  "aggregations" : {    "avg_rating" : {      "value" : 3.0    }  }}

参考资料

《极客时间》视频课之《Elasticsearch核心技术与实战》

版权声明

作者: 马瘦风(https://healchow.com)

出处: 博客园 马瘦风的博客(https://www.cnblogs.com/shoufeng)

(0)

相关推荐

  • 「Elasticsearch」ES重建索引怎么才能做到数据无缝迁移呢?

    背景 众所周知,Elasticsearch是⼀个实时的分布式搜索引擎,为⽤户提供搜索服务.当我们决定存储某种数据,在创建索引的时候就需要将数据结构,即Mapping确定下来,于此同时索引的设定和很多固 ...

  • ElasticSearch的学习笔记并整合SpringBoot做测试

    ElasticSearch的学习 简介 ElasticSearch是一个分布式的开源搜索和分析引擎,MySQL专攻于数据的持久化存储与管理(即CRUD),在真正要处理海量数据的检索与分析时,Elast ...

  • ElasticSearch使用

    前言 Lucene使用起来有点麻烦,其实现在企业中使用原生Lucene来进行搜索的很少了,使用Solr和ElasticSearch比较多,Solr和ElasticSearch都是基于Lucene开发的 ...

  • 一次看完28个关于ES的性能调优技巧

    后台回复'书',获取 来源:elasticsearch.cn/article/6202 因为总是看到很多同学在说Elasticsearch性能不够好.集群不够稳定,询问关于Elasticsearch的 ...

  • Flink在bilibili大数据与AI方向的探索与实践

    分享嘉宾:郑志升@bilibili 内容来源:Flink中文社区 导读:本文由 bilibili 大数据实时平台负责人郑志升分享,本次分享核心讲解万亿级传输分发架构的落地,以及 AI 领域如何基于 F ...

  • Excel中的数据建模:表间关系一线牵,何须大量公式拼数据

    小勤:现在的数据分析往往涉及好多个表,比如客户表.产品表.订单表.订单明细表等等,经常要结合起来分析,每次都要把一个表的数据匹配到另一个表里才能分析,岂不要累屎? 大海:在传统数据透视表里的确是要那么 ...

  • Power Pivot数据建模基础:数据表间的4种基本关系类型

    小勤:在前面<表间关系一线牵,何须大量公式拼数据>的文章里提到,如果产品表的产品名称重复的话,是不能建立表间关系的,这具体是什么情况? 大海:在数据处理的时候,我们经常需要对2张相关的表格 ...

  • 遵守好这五大准则,不愁做不好大数据建模

    在过去的几十年里,数据建模的努力通常集中在关系数据建模或可扩展标记语言(XML)的建模上.只要数据存储在关系数据库中,关系数据建模就会很好,但除此之外,它很少会有其他的用途.大数据建模是一个数据挖掘的 ...

  • 万字长文详解ETL和数据建模~!

    源 /         文/ 什么是ETL ETL是数据抽取(Extract).转换(Transform).加载(Load )的简写,它是将OLTP系统中的数据经过抽取,并将不同数据源的数据进行转换. ...

  • Power BI数据建模

    数据建模并没有那么高深,你同样可以学会!这篇文章通过一个实例创建一个简单的数据建模,并引出两个重要的概念:度量值和DAX. 之前谈论PowerBI与Power Pivot的关系时就提到,Power B ...

  • Power BI财务报表分析:数据建模篇

    PowerBI财务报表分析系列之: 数据建模篇 我们面对的源数据,往往并不是只有一张表,而这些不同的表,需要协同配合才能更有效的使用,多表的协同配合依靠表与表之间的逻辑关系. 根据分析的需求,在多个表 ...

  • PowerBI数据建模,为什么不建议你使用双向关系?

    文/陆文捷 物流供应链优化分析师,Power BI爱好者,知乎:Beethovenist 经常有伙伴会在星球中询问在数据模型中,如何通过事实表对维度表筛选计算的问题,此类需求在PowerBI中启用表之 ...

  • 数据分析基本步骤及常用工具,32页数据分析师培训,分析方法论

    为职场精英打造个人知识体系,升职加薪! 数据分析师培训 数据分析培训课程安排 1.初步认识数据分析 2.数据透视表(上机操作) 3.数据图表 4.数据分析报告 5.案例分析-数据分析在电话销售中的应用 ...