索引文件的生成(二十四)(Lucene 8.6.0)

  本文承接文章索引文件的生成(二十三)之fdx&&fdt&&fdm,继续介绍剩余的内容,先给出生成索引文件fdx&&fdt&&fdm的流程图:

图1:

索引阶段

是否生成一个chunk

图2:

  在上一篇文章中我们知道,在索引阶段,每处理一篇文档会将存储域的域值写入到bufferedDoc[ ]数组中并且增量统计存储域的信息,其中一个信息就是numBufferedDocs,它描述了在生成一个chunk之前,当前已经处理的文档数量。当前流程点的判断条件就依赖上文的描述,由于该条件的判断方式比较简单,故直接给出源码:

图3:

  图3中,bufferedDocs.getPosition( )方法描述了存储在bufferedDoc[ ]数组中的域值长度(占用字节数量),chunkSize是一个阈值,达到阈值后即满足第一种生成一个chunk的条件,chunkSize的值有两个可选项:16384跟61440,他们分别对应两种生成索引文件.fdt的模式:FAST跟HIGH_COMPRESSION模式,这两种模式描述了对bufferedDoc[ ]数组中的域值使用不同的压缩算法,分别是LZ4跟JDK中的Deflater。生成索引文件.fdt的模式可以通过实现抽象类Codec来自定义。

  第二种满足生成的条件为,其中maxDocsPerChunk也是一个阈值,同样有两个可选项:128跟512,分别对应上文中的生成索引文件.fdt的模式。

  故图3中满足流程点是否生成一个chunk的条件就是,目前处理的域值长度或者处理的文档数量是否超过阈值。

生成一个chunk

图4:

  在当前流程点生成一个chunk中,我们将把目前已经处理的信息(域值信息、存储域的信息)生成一个chunk,完成下面几个工作:

 

记录chunk的两个信息

  此流程中会使用两个临时文件记录这个即将生成的chunk的两个信息:

图5:

  图5中的两个临时文件会统计一个段中所有chunk的两个信息,在图1的flush阶段,将分别用于生成NumDocs的信息生成startPoints的信息这两个流程点。

  至于chunk的这两个信息的作用,我们随后将在索引文件的读取之fdx&&fdt&&fdm的文章中详细展开。

docBase、numBufferedDocs写入到索引文件.fdt中

  docBase是chunk中第一个文档的段内文档号、将被写入到索引文件.fdt的DocBase字段,numBufferedDocs将被写入到索引文件.fdt的ChunkDos字段,ChunkDos字段中还包含了sliceBit,该值是一个布尔类型,在下文中会具体介绍,故numBufferedDocs和sliceBit将作为一个组合值被写入到ChunkDos字段中,组合方式为,即ChunkDos的最低位用来存储布尔类型的sliceBit,如下所示:

图6:

存储域的信息写入到索引文件.fdt中

  在文章索引文件的生成(二十三)之fdx&&fdt&&fdm中我们知道,存储域的信息在内存中使用numStoredFields[ ]数组,endOffsets[ ]数组存储,他们分别将被写入到DocFieldCounts字段跟DocLengths字段,如下所示:

图7:

  numStoredFields[ ]数组,endOffsets[ ]数组中的信息被写入到索引文件.fdt之前,会先进行编码压缩处理,不同的处理方式使得DocFieldCounts、DocLengths有不同的数据结构,该内容在文章索引文件之fdx&&fdt&&fdm已经介绍,不赘述。

域值信息写入到索引文件.fdt中

  在文章索引文件的生成(二十三)之fdx&&fdt&&fdm中我们知道、域值信息在内存中使用bufferedDocs[ ]数组存储,它将被写入到CompressedDocs字段,如下所示:

图8:

  域值信息被写入到索引文件.fdt之前,会根据域值的总长度(占用字节数量)判断是否需要进行切片压缩,判断条件如下:

图9:

  图9中,当域值长度满足条件后,需要对域值进行切片压缩,将域值按照chunkSize划分为每一个切片,然后对每个切片使用LZ4算法(上)算法进行压缩处理。

  至此,在索引阶段生成索引文件fdx&&fdt&&fdm的流程点介绍完毕。从上文的介绍可以发现,有可能存在这么一种情况,添加完最后一篇文档后,还未满足图2中是否生成一个chunk的条件,也就是说,还有一些文档的域值信息、存储域信息未被写入到索引文件.fdt中,那么这些未处理的文档将会在flush阶段完成。

flush阶段

  在flush阶段,一方面将处理上文中说的未处理的文档的域值信息、存储域信息,另一方面还会根据图5中的两个临时文件中的信息生成索引文件.fdx、fdm。

  图1中flush阶段的流程在执行flush(IndexWriter.flush())的流程中的位置如下所示:

图10:

点击查看大图

  注意的是图10中除了图1中的流程,其他的流程都是基于Lucene 7.5.0,与Lucene 8.6.0可能会有些差异,但是生成索引文件fdx&&fdt&&fdm在flush阶段的时机点是相同的。

生成一个chunk

  这里生成一个chunk的逻辑跟图4中的流程点是一样,区别在于当前即将生成的chunk中的域值长度以及文档数量是不满足图2中的条件的,在执行完当前流程后,会统计增量统计一个变量numDirtyChunks,它随后将被写入到索引文件.fdt中。

生成NumDocs的信息

图11:

  在图1的索引阶段,使用了图5中的临时文件_0_Lucene85FieldsIndex-doc_ids_0.tmp存储了一个段中每个chunk中包含的文档数量,从上文的描述我们可以知道,一个chunk中的文档数量可能因为域值的长度而各不相同。

  处理过程为读取每一个chunk中的文档数量,每处理1024(2 << blockshift(blockshift的介绍见文章索引文件之fdx&&fdt&&fdm))个chunk就生成一个NumDocsBlock,然后写入到索引文件.fdx中:

图12:

  图12中NumDoc字段为某个chunk中的文档数量,最后NumDocsBlock会先进行编码再使用PackedInts(一)进行压缩处理后再写入到索引文件.fdx中。

  上文中说到每处理2 << blockshift个chunk就生成一个NumDocsBlock,此时还会生成NumDocMeta信息来记录编码信息,使得在读取阶段能通过NumDocMeta中的编码信息恢复成原数据NumDoc,NumDocMeta信息会被写入到索引文件.fdm中:

图13:

  图13中Min、AvgInc、Length、BitRequired为编码信息,其编码逻辑本文不会展开,编码的目的是为了能降低索引文件的文件大小,另外图13中的其他字段的介绍可以阅读文章索引文件之fdx&&fdt&&fdm

生成StartPoints的信息

图14:

  在图1的索引阶段,使用了图5中的临时文件_0_Lucene85FieldsIndexfile_pointers_1.tmp存储了一个chunk对应的数据块在索引文件.fdt中的起始读取位置。

  处理过程为读取每一个chunk中的对应的数据块在索引文件.fdt中起始读取位置,每处理1024(2 << blockshift(blockshift的介绍见文章索引文件之fdx&&fdt&&fdm))个chunk就生成一个StartPointBlock,然后写入到索引文件.fdx中:

图15:

  图15中StartPoint为某个chunk对应的数据块在索引文件.fdt中的起始读取位置。同样的,StartPointBlock会先进行编码再使用PackedInts(一)进行压缩处理后再写入到索引文件.fdx中。

  跟NumDocsBlock一样,每处理2 << blockshift个chunk就生成一个StartPointBlock,此时还会生成StartPointMeta信息来描述编码信息,使得在读取阶段能通过StartPointMeta中的编码信息恢复成原数据StartPoint,StartPointMeta信息会被写入到索引文件.fdm中:

图16:

  图16中Min、AvgInc、Length、BitRequired为编码信息,其编码逻辑本文不会展开。另外图13中的其他字段的介绍可以阅读文章索引文件之fdx&&fdt&&fdm

结语

  至此,生成索引文件fdx&&fdt&&fdm过程介绍完毕,在随后介绍读取索引文件fdx&&fdt&&fdm文章中,我们将对比Lucene 7.5.0版本的索引文件fdx&&fdt,介绍数据结构的差异以及优化。

点击下载附件