ES学习笔记之倒排索引

633 阅读6分钟

由于之前被面试官吊打过es的索引原理,所以本人对es的倒排索引进行一个学习和总结。
学习小册子:

Elasticsearch 从入门到实践 - spoofer - 掘金小册 (juejin.cn)

涉及到的数据结构和算法:

  1. 倒排索引
  2. FST(Finite State Transducers)
  3. 跳表
  4. bitmap
  5. 二分法查找
  6. ...

什么是倒排索引(Inverted Index)

索引从本质来讲就是一种为了加快检索数据的存储结构,就像书的目录一样,可以很快地帮助我们查找到某个章节所在的位置。
常见的索引有哈希表B+树有序数组等等,像这种实体 ID 到数据实体内容的关联关系的索引我们称为正排索引,正排索引非常适合处理键值查询的场景。
与正排索引不同,倒排索引是把保存词项到实体ID关系的索引,倒排索引设计中使用到了很多数据结构和算法,使查询效率更高,同时也能够处理大批量的数据。

倒排索引的组成

如果有以下几个短语:

  1. 倒排索引学习笔记
  2. 今天我要学习
  3. 好好珍惜今天的日子
词项文档数量文档id
倒排11
索引11
学习21,2
笔记11
今天22,3
我要12
好好13
珍惜13
13
日子13

如果我们要找包含学习的短语,没有索引的话,那么我们需要对文档进行遍历才能找到对应的文档id,为了提高效率需要为所有数据建立索引,比如倒排索引
虽然索引会对我们的数据进行排序,使我们筛选查询的时候定位到数据,一旦数据量大的时候,我们遍历的数据就很庞大,索引占用空间也很庞大,查询效率不高,就会面临以下几种问题:

  • 分词形成的词项(term)可能是海量的,需要可以在内存和磁盘上高效存储;
  • 既然词项是海量的,那么如何快速找到对应的词项也是个问题;
  • 每个词项对应的文档数可能非常多,也就是上图中文档列表的链表很长;
  • 在词项对应的文档多的情况下,多个文档列表间做交集的效率将是个挑战。

倒排索引为了解决以上几个问题,使用词项索引(Term Index)来解决上述 1 和 2 的问题,而对于 3 和 4,Lucene 对数据进行了压缩处理,使用 Roaring Bitmaps、跳表等技术来进行快速求交集。
倒排索引的组成主要有3部分, Term IndexTerm DictionaryPosting List 如下图:

Es组成部分.drawio.png

其中,Term Dictionary 保存的是词项,随着词项越来越多,从 Term Dictionary 中查找一个词项势必越来越慢,所以必须对这些词项做索引,从而有了 Term Index,它是 Term Dictionary 的索引,最好设计得越小越好,这样缓存在内存中也没有压力。Posting List 保存着每个词项对应的文档 ID 列表(其实还有其他信息,这里为了方便理解,只显示 ID)。

Term Index

Term Index作为Term Dictionary的索引,需要尽量确保资源消耗小,使之可以缓存在内存中,并且还要确保查询数据有较低的复杂度。
如下图所示,Term Index 只使用公共前缀做索引,那本身要存储 coach、cottage 两个字符串的索引,现在只需要存储 co 一个就行了。这样的话,拥有同一公共前缀的 Term 越多,实际上就越省空间,并且这种设计在查找的时候复杂度是公共前缀的长度:O(len(prefix)) 。对于前缀索引的实现,业界使用了 FST 算法来解决。FST(Finite State Transducers)是一种 FSM(Finite State Machines,有限状态机),并且有着类似于 Trie 树的结构,可以直接根据前缀就找到对应的term在词典中的位置。使用 FST有以下好处:

  • 通过对 Term Dictionary 数据的前缀复用,压缩了存储空间;
  • 高效的查询性能,O(len(prefix))的复杂度;
  • 构建后不可变。(事实上,倒排索引一旦生成就不可变了。)

事实上,FST算法很复杂,有时间我会再研究!!!

Es组成部分-第 2 页.drawio.png

Term Dictionary

Term Dictionary 保存着 Term 与 Posting List 的关系,存储了 Term 相关的信息,如记录了包含该 Term 的文档数量(DocFreq)、Term 在整个 Segment 中出现的频率等,还保存了指向 Posting List 文件的指针(文档 ID 列表的位置、词频位置等)。Term Dictionary为所有Term做好排序,并且进行分块保存,并且只保存Term的后缀。

Posting List

Posting List不仅保存了文档的Id,还有词频、位置等等,Lucene 把这些数据分成 3 个文件进行存储:

  • .doc 文件,记录了文档 ID 信息和 Term 的词频,还额外记录了跳跃表的信息,用来加速文档 ID 的查询;并且还记录了 Term 在 pos 和 pay 文件中的位置,有助于进行快速读取。
  • .pay 文件,记录了 Payload 信息和 Term 在 doc 中的偏移信息;
  • .pos文件,记录了 Term 在 doc 中的位置信息。

Posting List 主要面临着两个问题:

  • 如何节省存储?
  • 如何快速做交集?

1. 如何对数据进行压缩?

核心的思想都是:用最少的位数表示原数据,并且兼顾数据读取效率

  • PackedBlock
  • VIntBlock
  • 使用文档 ID 差值存储来节省空间

以上的方式的过程中,就是文档 ID 使用增量编码,数据分块存储、数据按需分配存储空间,而这整个过程叫做 FOR(Frame Of Reference)

2. 如何快速对数据求交集?

  • 位图
    使用缺点就是空间消耗大
  • Roaring Bitmaps
  • 使用跳表加速多个列表交集的求解

总结和思考

ES为了提高查询速率和节省空间,做了很多工作。
Es倒排索引组成包含:Term Index,Term Dictonary,Posting List.

  • Term Index 是 Term Dictionary 的索引,使用它可以快速判断一个 Term 是否存在,并且可以找到这个 Term 在 Term Dictionary 存储的块地址。Term Index 使用了 FST 来实现,其有着小巧且复杂度低的特点。
  • 而 Term Dictionary 是存储 Term 信息的地方,其内容包括 Term、包含该 Term 的文档的数量、Term 在整个 Segment 中出现的频率等。
  • 在 Posting List 的实现中,Lucene 使用 PackedBlock 和 VIntBlock 对整数进行压缩的思路,使用 Roaring Bitmaps 和跳表来解决列表求交集的方案,保证高频filter查询速度的同时降低存储空间消耗。