索引文件的生成(十五)之dvm&&dvd(Lucene 8.4.0)
Lu Xugang Lv6

  在前面的文章中,我们介绍了在Lucene7.5.0中索引文件.dvd&&.dvm的数据结构,从本篇文章开始介绍其生成索引文件.dvd&&.dvm的内容,注意的是,由于是基于Lucene8.4.0来描述其生成过程,故如果出现跟Lucene7.5.0中不一致的地方会另外指出,索引文件.dvd&&.dvm中的包含了下面几种类型:

  • BinaryDocValues
  • NumericDocValues
  • SortedDocValues
  • SortedNumericDocValues
  • SortedSetDocValues

  本篇文章从NumericDocValues开始介绍,建议先阅读下文章NumericDocValues,简单的了解NumericDocValues类型的DocValues的数据结构。

  在文章索引文件的生成(一)之doc&&pay&&pos中,简单的介绍了生成索引文件.dvd&&.dvm的时机点,为了能更好的理解其生成过程,会首先介绍下在生成索引文件之前,Lucene是如何收集每篇文档的NumericDocValues信息。

收集文档的NumericDocValues信息

  在源码中,通过NumericDocValuesWriter对象来实现文档的NumericDocValues信息的收集,并且具有相同域名的NumericDocValues信息使用同一个NumericDocValuesWriter对象来收集,例如下图中添加三篇文档:

图1:

  在使用NumericDocValuesField来生成NumericDocValues信息时要注意,在一篇文档中,仅能添加一条相同域名的NumericDocValuesField,但是能添加多条不同域名的NumericDocValuesField,如图1中,在文档0中添加了域名分别为"age"、"level"的NumericDocValuesField,如果我们在一篇文档中添加两个或以上相同域名的NumericDocValuesField,那么会抛出以下的异常:

图2:

图3:

  图3中抛出的异常对应的代码实际就是上文提到的NumericDocValuesWriter类中的:

图4:

  上文中说到相同域名的NumericDocValuesField对应的NumericDocValues信息使用同一个NumericDocValuesWriter收集,在收集的过程中,如果一篇文档中包含了两个或以上相同域名的NumericDocValuesField,如图4所示,docID <= lastDocId的条件就会成立,即抛出图3中的异常。

  在收集NumericDocValues信息的过程中,我们仅仅关心下面两个信息:

  • docId:即包含NumericDocValues信息的文档号
  • 域值:即NumericDocValuesField的域值,例如图1的文档0中,域名为"age"的NumericDocValuesField的域值为88

docId

  在源码中,使用DocsWithFieldSet对象收集文档号docId,使得在某个特殊条件(见下文流程点是否使用了FixedBitSet?的介绍)下,在索引阶段能更少的占用内存,在读取阶段有更好的读写性能

  我们通过介绍DocsWithFieldSet存储文档号的的流程来解释上述的内容:

图5:

  在介绍图5的流程之前,我们先介绍下图中几个变量:

  • lastDocId:该值描述的是DocsWithFieldSet上一次处理的文档号
  • cost:该值的初始值为0,它其中一个作用是描述DocsWithFieldSet已经处理的文档数量,其他的作用在下文中会介绍
  • FixedBitSet:用于存储文档号,见文章工具类之FixedBitSet的介绍

文档号

图6:

  图5的流程描述了DocsWithFieldSet存储一个文档号的过程,所以流程图的准备数据为一个文档号。

文档号是否不大于已收集的文档号?

图7:

  通过比较当前处理的文档号docId跟lastDocId的值来判断文档号是否不大于已收集的文档号,如果判断为否,那么直接抛出异常:

图8:

  当前流程点判断的目的是想说明DocsWithFieldSet只处理从小到大有序的文档号集合。

FixedBitSet

图9:

  介绍图9的流程点之前,我们先说下DocsWithFieldSet存储文档号的两种方式:

  • FixedBitSet:见文章工具类之FixedBitSet
  • cost:这里的cost即上文中提到的cost,它一方面描述了DocsWithFieldSet已经处理的文档数量,同时在某个特殊条件下,它也能用来描述DocsWithFieldSet存储的文档号集合(见下文介绍)

某个特殊条件是什么?

  该特殊条件指的是DocsWithFieldSet处理的文档号集合中的文档号是从0开始有序递增的,这意味着段(段内的文档号是从0开始有序递增的)中每一篇文档中都包含某个域名的NumericDocValues信息。

  例如图1中,如果添加了三篇文档后生成了一个段,那么对于域名为"age"的NumericDocValues信息就满足上述的特殊条件,而对于域名为"level"的NumericDocValues信息,由于文档1中没有添加这个域,那么就不满足。

满足或者不满足特殊条件有什么不同

  如果满足特殊条件,意味着我们不需要存储每个文档号,只需要知道cost的值就行了,cost的默认值为0,每处理一个文档号,cost的值就执行+1操作,那么在读取文档号阶段我们就可以根据cost获得文档号集合区间,即[0, cost]。

  如果不满足特殊条件,那么只能通过FixedBitSet来存储每一个文档号。

  可见如果满足了特殊条件,在索引阶段,我们就不需要额外使用FixedBitSet对象来存储文档号,即上文中提到的在索引阶段能更少的占用内存;同时在读取阶段,我们只要顺序遍历0~cost的值就可以获取文档号,而不需要通过FixedBitSet来读取文档号(见文章工具类之FixedBitSet),即上文中提到的在读取阶段有更好的读写性能

  回到当前流程点的介绍,如果流程点是否使用了FixedBitSet?为否,说明之前处理的文档号集合是从0开始有序递增的,如果为是,那么只能通过FixedBitSet存储文档号,即执行流程点使用FixedBitSet存储文档号,接着在流程点是否新建FixedBitSet?的判断中,通过比较当前处理的文档号docId跟cost值来进行判断:

  • docId 等于cost:说明目前仍然处于满足特殊条件的情况,即流程点是否新建FixedBitSet?的判断为假,那么直接更新lastDocId的值更新为dociId,用于处理下一个文档号时,判断图7的流程点文档号是否不大于已收集的文档号,接着更新cost的值,即cost++的操作,在这个操作之后,通过上文中的介绍可以知道,目前DocsWithFieldSet已经处理(存储)的文档号集合为[0, cost]
  • docId 不等于cost(docId与cost的差值大于1):说明从当前处理的文档号开始就不满足特殊条件了,即流程点是否新建FixedBitSet?的判断为真(是),那么此时需要执行流程点使用新建的FixedBitSet存储文档号,同时将之前处理的文档号(通过cost的值获得)以及当前文档号存储到新建的FixedBitSet对象中。当然还得继续更新lastDocId,因为在处理下一个文档号时,lastDocId要用于判断图7的流程点文档号是否不大于已收集的文档号;cost的值依然执行cost++的操作,因为cost的功能不仅仅是用来判断在特殊条件下,用来描述存储的文档号集合,它还要用来描述DocsWithFieldSet处理的文档号数量(见上文cost的介绍)

域值

  域值即NumericDocValuesField的域值,例如图1的文档0中,域名为"age"的域值为88,我们需要收集88这个域值。

  源码中使用了PackedLongValues来实现压缩存储,关于PackedLongValues的内容请参看文章PackedInts(一),本文不赘述,在后面的文章中,会再次介绍PackedLongValues的内容。

结语

   无

点击下载附件

 Comments