首页 > 其他分享 >ES的倒排索引和正排索引以及FieldData

ES的倒排索引和正排索引以及FieldData

时间:2023-03-03 17:00:26浏览次数:30  
标签:FieldData 倒排 doc 索引 文档 values fielddata

转:https://server.51cto.com/article/694939.html

 

1 基本概念简介

  所谓正排索引很简单,就是和我们人脑的记忆更加贴合的一种数据结构。

  比如记忆古诗,当别人问我们《静夜思》这首诗的时候,我们很容易就能够背出完整的诗句。

但是如果有人问我们哪一首诗里面包含有霜这个字的时候,我们就很难想到《静夜思》这首诗了。因为我们的大脑在记忆古诗的时候是建立了一个正排索引。

  静夜思→窗前明月光,疑是地上霜,举头望明月,低头思故乡

  而倒排索引是与这样的数据结构相反的,有一个从古代流传至今的游戏,叫做《飞花令》,规则就是要能够说出含有“花”的诗句,谁能够说的多谁就获胜。

  要能够在这样的游戏获胜,关键就是看谁能够在脑海中建立好关于花的倒排索引。比如:

  综上,这就是倒排索引,但是 ES 的倒排索引还要更加复杂,为了进行评分计算,ES 会增加一些对该词项的统计信息

 

2 正排索引doc values

2.1 为什么需要 doc values

2.1.1 引出问题

  假设有 3 个文档,各自有一个字段,三个文档如下

   那么它按照 name 建立的倒排索引会如下图所示

  现在,假设我们要做这么一个查询:查询出 name 含有后 brown 的文档,并且按照 age 排序。

  查询分析:因为我们有了 name 的倒排索引,我们直接看上述的倒排索引我们很快就可以知道命中的倒排索引是 Doc_1 和 Doc_2。之后我们要根据 age 进行排序,那么有什么方法可以做到呢?

  先逆向思考,为了能够排序我们需要什么呢?很简单,我们需要知道待排序的文档的每一个文档的文档 id 及其对应的 age。

  也就是说我们需要有doc_id→age这样的一个映射关系,那么问题就被转化了

  我们有什么方法可以得到这一个映射关系呢

 

2.1.2 方法一(访问磁盘)

1)简介

  在上面的查询中,我们已经过滤出待排序的文档是 Doc_1 和 Doc_2,那么我们可以访问磁盘取回这两个文档的数据,这样我们就可以建立 doc_id→age 的映射关系了。

  

2)缺点

  在示例中,我们只命中了两个文档,但是在真实的业务场景中,我们命中的文档就可能是非常非常多的。

  比如我们的的查询文档如果是中国的 14 亿人口,按照性别过滤而后按照年龄排序,那么我们要取回的文档数量就将达到 7 亿个文档之多,这样要取回的数据量就太大了。

  而且,可以想见,要取回命中的文档是属于随机 IO,这样的话此方案对于 IO,CPU,内存都有很大的压力,响应时间更是难以想象。

  结合方法一的缺点,我们发现访问源数据是很不友好的,那么如果不访问源数据且要用现有的资源要怎么做呢

 

2.1.3 方法二(访问倒排索引)

1)简介

  读取已有的倒排索引,利用倒排索引来建立 doc_id→age 的映射关系。根据倒排索引的数据结构,我们的操作变成:遍历整个倒排索引的所有词项,从而建立完整的 doc_id→age 映射关系。

 

2)缺点

  每次排序都需要遍历一遍倒排索引,当倒排索引的词项很少的时候还好,当词项很多的时候速度将会变慢。

  而且每次根据不同的查询条件,我们建立的 doc_id→age 的映射关系都不同,需要我们查询一次遍历一次,建立一次映射关系。简而言之,缺点是:建立映射麻烦,可复用性不高。

  

2.1.4 方法三(提前建立好doc_id→age 的映射关系)

  在方法2的基础上我们进一步剖析,我们希望在查询的时候能够更快速的获得 doc_id→age 的映射关系,且能够复用。

  对于 doc_id→age 的映射关系,我们是一定要建立的,既然这一步必不可少,那么我们可不可以对这个步骤进行分解呢?

  即分解成在文档被插入(官方文献中,文档被插入描述成文档被索引,笔者看多了官方文献,其实习惯描述成被索引,但这里还是说成被插入以免被误解)的时候,与倒排索引一起被创建

  在文档被插入的时候就建立 doc_id→age 的映射关系,需要排序和聚合的时候,我们只要直接读取就可以了。如上分析,引出了 ES 的 doc values,江湖人称正排索引

 

2.2. Doc values的特性

  经过一层层啰嗦的剖析,我们终于引出了 doc values,那么我们就来更加深入的认识 doc values

  1)生成时机:在文档被插入的时候与倒排索引同期生成。

  2)数据结构:doc values 其实就是倒排索引的转置,大概结构如下

            

  3)存储位置:磁盘。

  4)在什么粒度上会生成 doc values

    基于每一个 segment(ES 的索引数据在每一个分片内有又被分成了一个有一个的 segment,每一个 segment 最大存放 2^31-1 个文档)独立生成,且和倒排索引,以及 segment 一样是不可变的(为什么不可变,以及不可变如何应对文档变更是一个很长很长的故事,敬请期待)。

  5)默认开启,所以不需要我们操心,但是如果我们很明确一个字段是不会被用于排序和聚合的,我们可以在创建它的时候就关闭 doc values 以节省资源。

  6)使用方式:读取回内存。

  7)不适应 text 类型字段。此处插入 doc_id 的含义哈,文档是存放在 segment,一个 segment 是 doc 的数组内的,doc_id 指的是每一个文档在 segment 内的 index,而不是很多人以为的 _id

 

2.3 doc values 的数据放置和内存分配方案

  针对上面特性5和特性6,ES 为了让查询更快速,且更少的占用资源,防止 ES 节点因 OOM 问题而见马克思,做了一些其他的努力

  看上面的第 5 点,读取 doc values 的数据放置在内存,这个内存是应用内存还是系统内存呢?

  答案是系统内存,因为可以充分利用操作系统的虚存技术,也就是说 doc values 放置的内存并不受 JVM 管理。

  当系统内存充足的时候,会都放置在系统内存,当系统内存不足的时候利用操作系统的虚存技术建立与 doc values 文件的映射关系,只读取部分 doc values 的数据在内存中,根据内存淘汰策略进行读入和淘汰。

  也由此引出 ES 官方关于 ES 节点内存分配策略的一个方案

       

 

2.4 ES的数据压缩手段

2.4.1 简介

  另外,为了读写更加的快速,有没有办法使得 doc values 占用的内存更小呢?这里就要体现 ES 的众多数据压缩手段之一了。

  看上面的 doc values 的数据示例,我们发现在示例中对应的词项是数字,最小的数字是 100,最大的是 4200。

  为了存放下这些数字,我们需要给每一个数字分配多大内存空间呢?为了装下 4200,因为 2^12<4200<2^13,所以我们需要为每一个词项至少分配 13 bit 的空间,示例中总共 7 个 doc,至少需要 7*13=91 bit。

  有没有办法,针对这种情况,ES 的压缩方式是:发现这些数字具有一个最大公约数 100,于是把这些数字都除以他们的最大公约数

       

  这样之后数据范围就变成了 1-42,为了存放 42,我们需要 2^5<42<2^6。

  也就是说我们存放每一个数字只需要 6bit。最终存放 7 个数字需要 6*7=42bit,压缩了一倍

  这就是 ES 对于 doc values 的数据压缩方式之一,

 

2.4.2  对于数字的压缩方式

 

 

2.4.3 对于字符串的压缩方式

  接着产生另一个问题,上面介绍的压缩方式都是针对数字的,但是我的词项要是字符串文本怎么办?我们把字符串转换成数字不就行了?

  看官网的解释

  

 

3 FieldData

3.1 简介

  行文至此,让我们再回忆下 doc values 的特点 6(不适用于 text 数据类型)。

  那么,text 类型这种文本字段要是要排序,或者是要聚合,要咋整呢

  于是有了一个新的东西:fielddata。

  Fielddata 的数据结构可以理解为 text 类型字段的正排索引结构

  它解决了 doc values 不支持多值字符串的问题。

 

3.2 fielddata的特性

  1)内存管理和生成-常驻内存

    fielddata 与 doc values 不同,它的生成和管理都是在内存中生成的,且一般情况下不会被释放,因为构建它的代价十分高昂,所以我们使它常驻

 

  2)更占内存

    对 text 字段的数据进行分析和生成 fielddata 的过程会产生很多的词项,会占用很多的内存

 

  3)懒加载

    一个配置开启了 fielddata:true 的字段的在第一次被聚合之前,是不会生成 fielddata 的

 

  4)全加载

    这里有一个令人惊讶的地方。假设你的查询是高度选择性和只返回命中的 100 个结果。大多数人认为 fielddata 只加载 100 个文档。

    实际情况是,fielddata 会加载索引中(针对该特定字段的) 所有的 文档,而不管查询的特异性。逻辑是这样:如果查询会访问文档 X、Y 和 Z,那很有可能会在下一个查询中访问其他文档

 

  5)基于segment 建立

    与 doc values,倒排索引一样基于 segment 建立而不是基于整个索引建立

 

  6)默认关闭

    开启的话需要手动开启,使用 fielddata:true

 

3.3 fielddata的问题

  针对上面的前两个特点,引申出如下两个问题

  1)生成慢

  2)占空间

 

3.4 生成慢的解决方案

  对于生成慢,会导致这么一个问题:首次查询使用到某一个字段的 fielddata 的时候速度会很慢,如果针对这点是不能忍受的,可以对该字段的 fielddata 进行预加载。

只需要在字段的 mappings 下添加如下即可

  

 

3.5 占空间的解决方案

3.5.1 数据限制

  除了做数据压缩,为了放置我们的 ES 因为加载了太多的 fielddata 而 OOM 崩溃。

  我们需要对 fielddata 的数据做一些限制

  1)indices.fielddata.cache.size

    限制 fielddata 使用空间,控制为 fielddata 分配的堆空间大小,当超过 fielddata 占用的内存大小超过这个限度就会触发对 fielddata 的内存回收,回收策略 LRU。

可以是百分比 20% 或者是具体值 5gb;有了这个设置,最久未使用(LRU)的 fielddata 会被回收为新数据腾出空间

 

  2)indices.breaker.fielddata.limit

    fielddata 内存使用断路器,断路器默认设置堆的 60% 作为 fielddata 大小的上限。超过这个上线会触发一个异常。一个异常好过当我们内存不足的时候出现 OOM 导致节点崩溃

 

  3)indices.breaker.request.limit

    request 断路器估算需要完成其他请求部分的结构大小,例如创建一个聚合桶,默认限制是堆内存的 40%

 

  4)indices.breaker.total.limit

    total 揉合 request 和 fielddata 断路器保证两者组合起来不会使用超过堆内存的 70%。

 

  注意:indices.fielddata.cache.size 和 indices.breaker.fielddata.limit 之间的关系非常重要。

  如果断路器的限制低于缓存大小,没有数据会被回收。为了能正常工作,断路器的限制 必须 要比缓存大小要高。

 

3.5.2 Fielddata 的过滤

  还有一个方案可以减少 fielddata 的数据大小,那就是数据过滤,把没有必要放入 fieldata 的数据过滤掉。

  比如我们对 100W 首歌曲进行按照标签 group 并取前 10,那么大概摇滚,嘻哈,流行之类的会排在前面,同时也会存在一些标签,比如“时长超过 20min”,这样的小众标签是几乎不会被查询到和聚合到的。

  那么就可以省掉这部分数据,不加载入 fielddata,甚至可以说很多数据可能符合正态分布,只有一小部分数据是经常被用来聚合的,其他的很多数据关联的文档特别少。

过滤方式如下:

  1)fielddata 关键字允许我们配置 fielddata 处理该字段的方式。

  2)frequency 过滤器允许我们基于项频率过滤加载 fielddata。

  3)只加载那些至少在本段文档中出现 1% 的项。

  4)忽略任何文档个数小于 500 的段。太小的段关键词所占的比例失衡

PUT /music/_mapping/song 
{ 
  "properties": { 
    "tag": { 
      "type": "string", 
      "fielddata": {   (1) 
        "filter": { 
          "frequency": {   (2) 
            "min":              0.01,  (3)  
            "min_segment_size": 500  (4) 
          } 
        } 
      } 
    } 
  } 
} 

 

标签:FieldData,倒排,doc,索引,文档,values,fielddata
From: https://www.cnblogs.com/jthr/p/17176267.html

相关文章

  • oracle查看索引及约束,表名
    查看索引SELECTT.*,I.INDEX_TYPEFROMUSER_IND_COLUMNST,USER_INDEXESIWHERET.INDEX_NAME=I.INDEX_NAMEANDT.TABLE_NAME=I.TABLE_NAME----指定表ANDT.T......
  • Pandas使用时间索引筛选时报错 AssertionError: <class 'numpy.ndarray'>
    Icameacrossthesimilarproblem,mysolutionwas:makesuretheindexistypeof'DatetimeIndex',Idothis:df.index=pd.to_datetime(df.index)sortedth......
  • 索引分类
    聚集索引(聚簇索引)(ClusteredIndex)将数据存储与索引放在一起,索引结构的叶子节点保存了行数据,必须有,而且只能有一个二级索引(非聚餐索引)(SecondaryIndex)将数据与索引分......
  • mysql索引结构
    Mysql索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包括以下几种:B+Tree索引:最常见的索引结构,大部分存储引擎都支持B+Tree索引。Hash索引:底层数据结构是用Hash......
  • 谷歌seo独立站搜索引擎优化指南【2023新版】
    作为一个拥有十年操作经验的个人站长,我认为SEO是网站优化的核心,它可以帮助我们的网站在搜索引擎上获得更高的排名和更多的流量。在本篇文章中,我将分享我的谷歌SEO独立站搜索......
  • KingbaseES sys_restore 恢复表时默认不包括表上的索引
    前言最近碰到一个案例,在使用sys_restore恢复指定表时,默认不恢复表上的索引,如果想恢复需要单独指定。测试过程[](javascript:void(0)......
  • mysql索引的面试常问问题
         ......
  • 索引
    如果没有索引的话,通常就是遍历整张表聚集索引:通过主键来找到符合条件的值非聚集索引:通过条件找到数据,然后通过聚集索引来找到表中的主键,之后在返回数据覆盖索引:查询的的......
  • TDengine 3.0.2.5 查询再优化!揭秘索引文件的工作原理
    TDengine3.0虽然对底层做了大规模的优化重构,但是相对于数据文件的工作逻辑和2.0相比是整体保持不变的。本系列文章的主旨在于帮助用户深入理解产品,并且拥有基本的性能调......
  • 优化索引
    SELECTt4.name,t1.[statement],t1.object_id,t2.user_seeks,t2.user_scans,t1.equality_columns,t1.inequality_columns,t1.included_columns,case--whent1.equali......