Elasticsearch Nested 选型,先看这一篇!

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 1、关于Nested 问题上次讲解了Elasticsearch 数据建模之后,很多同学反馈问题:Q1:用nested做嵌套文档,对嵌套文档的字段做查询,只要主文档下一个嵌套文档符合要求,就会把主文档以及下面的所有的嵌套文档都查出来,实际我只需要符合要求的嵌套文档。这个用nested可以做吗?Q2:请教个问题 这个nested 是只要用这个字段类型就影响性能还是说当只有用这个字段类型去筛选才影响性能?Q3:Elasticsearch Nested 取一条数据 怎么搞?Q4:nested聚合查询等,导致jvm内存剧增,出现长时间的full GC,如何破?介于此,非常有必要将

2、Nested 产生的背景

博文“干货 | Elasticsearch Nested类型深入详解”有讲过,这里再强调一遍。


Object 对象类型对数据拉平存储,不能实现精准检索匹配,会召回不满足查询结果的数据,才导致 Nested 的出现。


举例如下:


PUT laptops-demo/_doc/1

{

 "id": 1,

 "name": "联想ThinkPad P1(01CD)",

 "price": 29999,

 "brand": "Lenovo",

 "attributes": [

   {

     "attribute_name": "cpu",

     "attribute_value": "Intel Core i9-10885H"

   },

   {

     "attribute_name": "memory",

     "attribute_value": "32GB"

   },

   {

     "attribute_name": "storage",

     "attribute_value": "2TB"

   }

 ]

}

GET laptops-demo/_search

GET laptops-demo/_search

{

 "query": {

   "bool": {

     "must": [

       { "match": { "attributes.attribute_name": "cpu" }},

       { "match": { "attributes.attribute_value":  "32GB" }}

     ]

   }

 }

}

如上逻辑混乱的检索条件(CPU,8GB 内存混搭的条件),也能召回数据,这不是实际业务需要满足的场景。


本质原因:默认不指定 Mapping,如上的 attributes 里的键值对元素映射为:Object 类型。


而 Object 存储类似是单个文档拉平存储的,如下所示:


{

"id": 1,

"name": "联想ThinkPad P1(01CD)",

"price": 29999,

"brand": "Lenovo",

"attributes.attribute_name": ["cpu", "memory", "storage"],

"attributes.attribute_value": [“Intel Core i9-10885H”, “32GB”, “2TB”]

}

有了 Nested 类型,则可以很好的规避上述检索不精确的问题。


3、Nested 如何存储数据的?

先将一下上小节例子,用 Nested 类型实现如下:


注意:若选型 Nested,必须 Mapping 层面明确设定 Nested。


PUT laptops-demo

{

 "mappings": {

   "properties": {

     "id": {

       "type": "long"

     },

     "name": {

       "type": "text",

       "analyzer": "ik_max_word"

     },

     "price": {

       "type": "long"

     },

     "brand": {

       "type": "keyword"

     },

     "attributes": {

       "type": "nested",

       "properties": {

         "attribute_name": {

           "type": "keyword"

         },

         "attribute_value": {

           "type": "keyword"

         }

       }

     }

   }

 }

}

还是插入之前数据。


PUT laptops-demo/_doc/1

{

 "id": 1,

 "name": "联想ThinkPad P1(01CD)",

 "price": 29999,

 "brand": "Lenovo",

 "attributes": [

   {

     "attribute_name": "cpu",

     "attribute_value": "Intel Core i9-10885H"

   },

   {

     "attribute_name": "memory",

     "attribute_value": "32GB"

   },

   {

     "attribute_name": "storage",

     "attribute_value": "2TB"

   }

 ]

}

这里想说明的是:对于如下的拼凑且逻辑混乱的检索条件,不再返回数据。


GET laptops-demo/_search

{

 "query": {

   "nested": {

     "path": "attributes",

     "query": {

       "bool": {

         "must": [

           {

             "match": {

               "attributes.attribute_name": "cpu"

             }

           },

           {

             "match": {

               "attributes.attribute_value": "8GB"

             }

           }

         ]

       }

     }

   }

 }

}

只有将上面的 “8GB” 换成与 cpu 对应的属性值:“Intel Core i9-10885H”,才会有数据召回。


这体现了 Nested  的好处,实现了对象元素的“精准打击(检索)”。


嵌套对象将数组中的每个对象作为单独的隐藏文档(hidden separate document)进行索引。


拿上面的“联想电脑”的例子,用户看到写入了一个文档(对应文档id为1,包含三组attributes属性对象数据),实际后台是:4 个 Lucene 文档。


4个 Lucene 文档组成:1个父 Lucene 文档 (或者叫做:根文档)+ 3 个 Lucene 隐藏文档(nested 对象文档)。

image.png

具体文档存储大致拆解如下所示:


父文档或根文档


{

"id": 1,

 "name": "联想ThinkPad P1(01CD)",

 "price": 29999,

 "brand": "Lenovo"

}

第二个文档


{

 "attributes.attribute_name": "cpu",

 "attributes.attribute_value": "Intel Core i9-10885H"

}

第三个文档


{

  "attributes.attribute_name": "memory",

   "attributes.attribute_value": "32GB"

}

第四个文档


{

     "attributes.attribute_name": "storage",

     "attributes.attribute_value": "2TB"

}

这意味着:如果 attributes 键值对增多,假设上述示例中电脑的相关属性有100个,那么后台就会有:101 个文档。这看似风平浪静的存储,实际造成的风险还是很大的。


同样的一份文档写入,若包含 Nested 类型,则意味着有 N 个Nesed 子对象,就会多出 N 个 隐形文档,写入必然会有压力。


同样的,检索原来一个文档搞定,现在要在根文档及周边的 N 个隐形文档附件召回数据,势必检索性能也会受到影响。


大家似乎能隐约感觉到了:写入慢、更新慢、检索慢的问题所在了!


4、Nested 实战问题及解答

4.1 Nested 新增或更新子文档操作,为什么需要更新整个文档?

嵌套 Nested 文档在物理上位于根文档旁边的 Lucene 段中。这是为什么当只想更改单个嵌套文档时必须重新索引根文档和所有嵌套 Nested 文档的原因。


4.2 Nested 文档和父子文档(join 类型)本质区别?

6.X 之前的版本:父子文档是独立不同 type 实现,6.X 之后版本父、子文档要必须位于同一个分片上。


Nested 的存储不同之处如前所述:嵌套文档和父文档(根文档)必须在段中依次相邻。


这样对比下来,仅就更新操作:Join 父子文档是独立的,可以独立更新;而 Nested 嵌套文档需要全文档进行重建(类似:reindex 操作)。


4.3 为什么 Nested 写入和更新操作会很慢?

当需要:新增数据、修改数据的时候,看似更新子文档,实际整个document 都需要重建(类似:reindex 操作),这是更新慢的根源。


因为频繁的更新会产生很多的嵌套文档,则创建的 Lucene 文档量会很大。


5、Nesed 选型注意事项

第一:如果需要索引对象数组而不是单个对象,请先考虑使用嵌套数据类型Nested。


第二:Object、Nested 选型注意:如果不需要对 Nested 子文档精确搜索的就选型 object,需要的选型 Nested。


第三 :对于子文档相对更新不频繁的场景,推荐使用:Nested 类型。注意:Nested 需要 Nested 专属 query,Nested 专属聚合实现检索和聚合操作。


第四:对于子文档频繁更新的场景,推荐使用 Join 父子文档,其特点是:父、子文档分离,但在同一分片上,写入性能能相对快,但检索性能较 Nested 更慢。


Join 类型检索的结果不能同时返回父子文档,一次 join 查询只能返回一种类型的文档(除非:inner_hits)。


第五:早期版本 Github issue 有提及:inner_hits 检索的性能问题,新版本已有改善,但使用也要注意,使用前做一下:不加 inner_hits 和加 inner_hits 的响应时间比较。


https://github.com/elastic/elasticsearch/issues/14229


第六:为避免 Nested 造成的索引膨胀,需要灵活结合如下两个参数,合理设置索引或者模板。


更新 Mapping 操作如下:


PUT products/_settings

{

 "index.mapping.nested_fields.limit": 10,

 "index.mapping.nested_objects.limit": 500

}

index.mapping.nested_fields.limit 含义:一个索引中不同的 Nested 字段类型个数。


如果超出,会报错如下:


 "reason" : "Limit of nested fields [1] has been exceeded"

index.mapping.nested_objects.limit 含义:一个 Nested 字段中对象数目,多了可能导致内存泄露。


这个字段,在我验证的时候,没有达到预期,后面我再通过留言补充完善。


第七:选型 Nested 类型或者 Join 类型,就势必提前考虑性能问题,建议多做一些压力测试、性能测试。


6、小结

多表关联是很多业务场景的刚需。但,选型一定要慎重,了解一下各种多表关联类型:Nested、Join 的底层存储和逻辑,能有助于更好的选型。


Nested 本质:将 Nested 对象作为隐藏的多个子文档存储,一次更新或写入子文档操作会需要全部文档的更新操作。


上周末深圳的 meetup 提及:整合 mongoDB + ES 的解决方案(见文末社区参考链接),mongo DB 实现了多表融合,也就是大宽表一步到位,本质是:空间换时间的方案。该方案我认为脑洞非常大,如果业务层面可行,大家可以借鉴。


您使用 Nested 遇到哪些问题?欢迎留言交流。


参考

https://medium.com/codex/learn-advanced-crud-and-search-queries-for-nested-objects-in-elasticsearch-from-practical-examples-7aebc1408d6f


https://discuss.elastic.co/t/whats-nested-documents-layout-inside-the-lucene/59944


https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html


https://discuss.elastic.co/t/nested-types-are-slower/90546


https://elasticsearch.cn/slides/284

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
8月前
|
存储 JSON 数据建模
Elasticsearch数据建模实战之基于nested object实现博客与评论嵌套关系
Elasticsearch数据建模实战之基于nested object实现博客与评论嵌套关系
|
11月前
|
机器学习/深度学习 分布式计算 自然语言处理
【搜索引擎选型】Solr vs. Elasticsearch:怎么选?
【搜索引擎选型】Solr vs. Elasticsearch:怎么选?
|
11月前
|
数据建模
白话Elasticsearch59-数据建模实战_ Nested Aggregation/ Reverse nested Aggregation对嵌套的博客评论数据进行聚合分析
白话Elasticsearch59-数据建模实战_ Nested Aggregation/ Reverse nested Aggregation对嵌套的博客评论数据进行聚合分析
51 0
|
11月前
|
存储 JSON 数据建模
白话Elasticsearch58-数据建模实战_基于nested object实现博客与评论嵌套关系
白话Elasticsearch58-数据建模实战_基于nested object实现博客与评论嵌套关系
56 0
|
11月前
|
机器学习/深度学习 分布式计算 自然语言处理
【搜索引擎选型】Solr vs. Elasticsearch:选择开源搜索引擎
【搜索引擎选型】Solr vs. Elasticsearch:选择开源搜索引擎
|
存储 SQL 算法
|
存储 Java API
深入了解ElasticSearch的Nested数据类型
ElasticSearch中可以将数据以对象的方式存储并查询,但是ES底层的Lucene 没有内部对象的概念,因此如果通过默认的方式往ES中插入对象,ES会将对象层次结构扁平化为字段名称和值的简单列表。 比如下面这一段数据:
|
API 索引 存储
干货 Elasticsearch方案选型必须了解的10件事!
Elasticsearch 目前被广泛使用,也越来越受到欢迎。一些传统的行业甚至婚庆公司都已经在使用Elasticsearch。
1698 0
|
存储 SQL JSON
|
搜索推荐 定位技术 Apache
Elasticsearch中nested聚合操作
在Elasticsearch实战场景中,我们或多或少会遇到嵌套文档的组合形式,反映在ES中称为父子文档。 父子文档的实现,至少包含以下两种方式: 1)父子文档 2)Nested嵌套类型
854 0

热门文章

最新文章