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

Point/Range 类型写入详解

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

1. 写入主要流程

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

  • 根据field name构造PerField, PerField保存了每一个filed基本上所有的元数据, PerField数组组织形式如下:
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类型来说,这个不需要关心。
  • 根据PerField信息,构造维度信息;接着构造PointValuesWriter对象, 这个对象负责写Point数据,并将这个对象赋值给PerField的pointValuesWriter成员变量

  • 将文档的id与point值存入PointValuesWriter, 其中Point值会以ByteRef形式存储,这实际上是byte[]的一个封装。
    Doc id会存入docID的int数组,下标为该point的序号,即第几个point值
    Point会存入bytes的ByteBlockPool, 这个对象是一个可变的byte[][], 每个point 会转化成byte[]存入bytes, 因为point是固定字节,因此不需要设置offset与length。

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);
     // ...
  • DefaultIndexingChainflush函数进去后,对于point类型来说,主要会调用
    1. writeNorm() //没有显示设置的话,这个不起作用, 主要作用是写入Norm值,关于Lucene中Norm作用请google
    2. writeDocValues() //对于point类型来说这个不起作用, 主要记录像String, Filed docId到value的正向索引、NumericType与binary 对应的值。
    3. writepoint() 这个函数会将所有的point filed数据写入文件, 包括数据与索引
    4. 写入String, text field类型的数据、索引
    5. 构造segment文件中关于所有field信息的元数据

现在重点关注第3步过程

  • 后面调用链路如下:
    DefaultIndexingChain.writePoints() --> PointValuesWriter.flush() --> Lucene60PointsWriter.writeField() --> BKDWriter.writeField()

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类型数据存储与索引的核心。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容