Lucene Point/Range类型存储流程与文件格式

2019-09-21  本文已影响0人  ni_d58f

Point/Range 类型写入详解

(本文示例都是简单的一维IntPoint)

1. 写入主要流程

说明: 对Point来说,在DefaultIndexingChainprocessField方法中,只走到了indexPoint方法, 其中主要步骤有:

PerField Array 组织形式

主要算法: 根据fieldName hash取模得到数组下标index, 然后在对应的PerFiled中查找对应的PerFiled(如果index所对应PerField值不为null), 这个实现很类似于java的Map中根据key查找value过程。

关于为什么不直接使用Java 的Map, 作者在Java doc说根据测试表明采用数组 + 链表然后手动扩容方式相比于Map来说有3%的性能优势

PerField对应的数据结构:

PerField结构
PerField为 DefaultIndexingChain的内部类, 其中有关数据数据结构如图:

其中需要注的的有:

  1. fieldInfo //field元数据
  2. PointValuesWriter //写PointValue类
    像InvertState, TermHashPerFiled、docValuesWriter等对于Point类型来说,这个不需要关心。
byte[][]数组

具体代码如下:

public void addPackedValue(int docID, BytesRef value) {
    if (value == null) {
      throw new IllegalArgumentException("field=" + fieldInfo.name + ": point value must not be null");
    }
    if (value.length != packedBytesLength) {
      throw new IllegalArgumentException("field=" + fieldInfo.name + ": this field's value has length=" + value.length + " but should be " + (fieldInfo.getPointDataDimensionCount() * fieldInfo.getPointNumBytes()));
    }

    if (docIDs.length == numPoints) {
      docIDs = ArrayUtil.grow(docIDs, numPoints+1);
      iwBytesUsed.addAndGet((docIDs.length - numPoints) * Integer.BYTES);
    }
    bytes.append(value);
    docIDs[numPoints] = docID;
    if (docID != lastDocID) {
      numDocs++;
      lastDocID = docID;
    }

    numPoints++;
  }

现在将Point值存储内存的过程就结束了,是不是看起来很简单呢? 确实, 将数据保存在内存中的逻辑相对简单,复杂的过程在最后flush中程中

Flush过程

flush操作一般是由于用户显示调用commit()方法, 其它可能的情况包括内存缓存的数据过多,内存不足、合并段文件等,下面以用户显示调用IndexWriter.commit() 来说明Point类型说明对应的过程。

flush操作总入口在DocumentsWriterPerThreadflush方法中, 主要方法为

     //... 
     sortMap = consumer.flush(flushState);
     // ...

现在重点关注第3步过程

BKDWriter.writeField()会调用BKDWriter.add()BKDWriter.finish()方法。 代码链路比较长,直接贴最后的文件格式,后面会对文件格式具体内容进行说明

笔者测试代码如下:

 for (int i = 0; i < 10240; i++) {
       Document d = new Document();
       IntPoint p = new IntPoint("age", i);
       d.add(p);

       writer.addDocument(d);
  }
  writer.commit();

最终的存储形式如下图:

Point索引元数据文件组织形式

Point Filed的文件组织形式
Point索引元数据文件组织形式 Point值的BKD索引

dii文件保存的是block数据在dim文件的偏移量,查询的时候首先加载dii文件,根据dii文件同时加载dim文件的packedIndex区,拿到BKD的索引数据以构造BKD查询树。

查询的时候访问BKD(Block K Dimension)查询树,确定Block下标,比如查询1025这个值,可以很明确的知道在以1024为起始值的block中,通这该block的下标在dii文件中获取block在dim文件的地址,最后采用二分的方式找到对应的point

以上三张图大致展示Point field的存储结构及细节,关于Point field需要特别掌握BKD算法思想与实现
这是实现Point/Range类型数据存储与索引的核心。

关于具体代码流程与细节地方读者如果有兴趣,有可以自行参考上图去阅读。

上一篇下一篇

猜你喜欢

热点阅读