Lucene DocValues详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zteny/article/details/60633374

今天斗胆来试试DocValues,对于DocValues我想大家都不会觉得陌生,同时又不是非常熟悉,就是那种熟悉而又陌生的感觉。

一、docValues是什么鬼呢?

DocValues在LUCENE-3518才引入新特征,初生在Lucene4.0,由Mike(Michael McCandless)提出的。从此Lucene牛逼轰轰了。

  • 先来介绍一下什么是倒排索引(反向索引),什么是正向索引。
  1. 倒排索引
    倒排索引,又称反向索引。故名思义是从term反向找到文档,存储结构是词条对应含有该词条的文档集。倒排索引是全文检索的基础,让全文检索的效率大大的提高。通常使用倒排索引来避免在每篇文档中使用冗长的顺序查找查询词。
  2. 正向索引
    正向索引,即是通过文档号找到字段的。

1.1 倒排那么好,为什么还需要正向索引呢?

接下来一段的话,大神略过。我们再来回顾一下倒排索引的搜索排序过程

我们已经知道倒排表存储了term 对就在的 docIds,也就是说我们可以用它非常高效的找到所有含有查询词的文档得到一个结果集。这个结果集含有满足查询条件的docid(即文档号),这个结果集极有可能非常大。
这里有两个非常重要的件:1. 结果集只有含文档号,不含文档的内容;2. 这个结果集很大,有很多个文档号。
当然一般来我们并不需要整个结果集,只需要按一定条件topK。

  1. 一定条件是指按相似度(Similarity得分)排序时,倒排索引依然非常完美。
  2. 一定条件是指除相似度排序之外,还需要依赖文档的一个或者多个字段时(即sort=f desc),其实是不是必须读取原文档,然后对应字段拿出来排序呢?答案是肯定。

所以倒排索引很好,但好像并不能解决一切问题。

  1. 对索引文件的变小了,之前需要存储了全文档,现在单字段。——可以减少移动的长度
  2. 少了读到文档要还要进行二次解释找到字段值的过程。—— 减少运算

对应此场景这个优化过程看似并不起眼,实际上在数据比较大的时候效果还是比较显著的。
按正向索引的定义,正向索引就是上面的优化方案,通过文档号直接找到字段值。因此正向索引又称为列存储

二、正向索引

solr docs对docValues的说明如下:

The standard way that Solr builds the index is with an inverted index. This style builds a list of terms found in all the documents in the index and next to each term is a list of documents that the term appears in (as well as how many times the term appears in that document). This makes search very fast - since users search by terms, having a ready list of term-to-document values makes the query process faster.

For other features that we now commonly associate with search, such as sorting, faceting, and highlighting, this approach is not very efficient. The faceting engine, for example, must look up each term that appears in each document that will make up the result set and pull the document IDs in order to build the facet list. In Solr, this is maintained in memory, and can be slow to load (depending on the number of documents, terms, etc.).

In Lucene 4.0, a new approach was introduced. DocValue fields are now column-oriented fields with a document-to-value mapping built at index time. This approach promises to relieve some of the memory requirements of the fieldCache and make lookups for faceting, sorting, and grouping much faster.

本文主要是想对DocValues的原理进行深度的剖析,根据官方文档和源码进行深入了解和解读。

2.1 DocValues存储结构

官方文档都有提到,DocValues字段是一个面向列存储的字段,一个Segment只有一个DocValues文件。也就是被DocValues标记的字段在建索引时会额外存储文件到值的映射关系,存储这个映射的文件叫DocValues data,简称**.dvd**。对应的元数据文件叫**.dvm**:DocValues Metadata。

这里metadata相当于Index文件,用来存储数据的索引,也就是存储数据文件每条记录起始位置以及记录的长度,同时存储数据文件的数据格式等信息(即是元信息)。

所谓面向列存储,相信大家都不会陌生了,市场上也有好多成熟的列存储框架诸如:Parquet、ORC等。

我们先看一下Parquet文档给出来的结构图,非常清晰
enter image description here

这是Parquet的存储结构,实际上我对Parquet并不是很熟悉,所以不能细说也不能妄做对比。

过去Lucene的文档会对Lucene的每种文件的格式进行深度的介绍说明,但不知道为什么Lucene自4.0版本开始引入DocValues至今Lucene已经发行了Lucene6.4了,还是没有对增加两种文件(即是前面提及的dvd和dvm)文件存储结构加入相关说明。

因此也只能大体来看这东西,如果想详细来读DocValues Data/DocValues Metadata还挺难的。同时放在这里吧篇幅又太长,所以打算另找时间再谈这个东西。

一个Segment一般情况下为DocValues生成两个文件(dvd,dvm),不管一个文档含有几个docValues字段,它们是什么类型都只会把数据存储在后缀为dvd的文件上。然后文件内容的组织方式是按字段,即先把Column A完全写入之后再写Column B,如此依次写到文件上。

因此才Write和Consumer两个角色。
Writer只负责把DocValues字段按对应DocValues类型写入到内存上,最后在Segment冲刷的时候,由Consumer把Writer的内容写入磁盘。至Lucene6.4,Lucene支持五种DocValues类型的,它们分别是Numeric、Binary、Sorted、Sorted Set、Sorted Numeric。对应也有五种类型的Writer,当然它们的格式也不尽相同。详见

lucene index的生产和消费
writer/reader都是逻辑上读写,并没有发生IO;consumer/producer则是物理上读写,会发生IO。这个跟我们认识可能有点不一样,总觉得读索引是消费。但站在程序的角度来讲,读到内存是生产、从内存到磁盘是消费,跟Stream的视角一样。

  1. consumer对于write完成持久过程,将缓存在writer的内容写入索引文件。
  2. producer对于reader完成反序列化过程,把索引入件读到Reader缓存。

这个结论有些草率,它并不是一般结论。对于writer与consumer成对出现的满足这个规律,至少DocValues是这样的。

2.2 DocValues Type

如此机智的你一定会想问,Lucene/Solr提供这五种类型的Writer(DocValues类型),但似乎并没有地方可以配置指定字段用什么类型DocValues。
对的,没有错,对Solr就是没得配置,由Solr根据你文档的类型自动帮你指定了。对于Lucene而言,没试过。

按国际惯例先来看一下官方文档给出的说明,

DocValues are only available for specific field types. The types chosen determine the underlying Lucene docValue type that will be used. The available Solr field types are:

  • StrField and UUIDField.
  1. If the field is single-valued (i.e., multi-valued is false), Lucene will use the SORTED type.
  2. If the field is multi-valued, Lucene will use the SORTED_SET type.
  • Any Trie* numeric fields, date fields and EnumField.
  1. If the field is single-valued (i.e., multi-valued is false), Lucene will use the NUMERIC type.
  2. If the field is multi-valued, Lucene will use the SORTED_SET type.
  • Boolean fields

These Lucene types are related to how the values are sorted and stored.

这说明让我很诡异,按理不是这样的,唯一解释是这方案没有更新。如何这么说呢,因为Lucene-5748即Lucene4.9时才加入新特征叫SortedNumericDocValuesWriter,同时我还翻阅了Solr6的源码,结论是对于Trie*Field用的不是NumericDocValuesWriter,而是SortedNumericDocValuesWriter。当然在multiValue="true"的情况下,依然采用SortedSetDocValuesWriter。

因此我认为Solr对DocValuesType适配情况应该如此:

  1. Numeric:NumericDocValuesWriter
    对于一般的integer/long/float/double/date都采用NumericDocValuesWriter
  2. Binary:BinayDocValuesWriter
    对应BinaryField,并且它会被转成byte[]进行存储
  3. Sorted:SortedDocValuesWriter
    对应String
  4. Sorted Set:SortedSetDocValuesWriter
    对应StrField字段,但仅限于multiValue="true"的情况
  5. Sorted Number:SortedNumericDocValuesWriter
    对应所有Trie*Field,包括TrieIntField和TrieDateField

因此docValues不支持TextField类型

2.3 docValues用法的应用情场

对DocValues用法的话,可以直接参考Solr User Guider。当然对于应用情场也是可以直接参考Solr User Guider的。这里我们也不能啥都说是吧,主要还是关注一下应用情场吧。

DocValues解决是从文档号到值的映射关系的问题,前面也谈到了什么情况下需要用到正向索引。即是在大量结果集里通过少量字段进行大规模筛选过滤候选文档时,DocValues可以显著减少IO时间和降低运算量。

那么问题来,就是“你说的我都懂,可我就不知道那些操作属于这种情呢?”。官方文档说,Facet/Group By/Sort/部分Function Search。
其实也不难理解,我们一个一个来看:

  1. facet
    facet名为切面检索,其实从这个名称里很难看出什么端倪来。但我们一般说Facet是字段分类统计,即是搜索结果以按指定字段按它的值进行分类统计。如果按这个说法的话,我们应该很好理解DocValues下Facet会更高效。因为它在搜索结果里按字段的值进行分类统计,所以可以说它主要关注文档中具体某个字段的值。
  2. group by
    Group By,即是分组。我们都知道分组的依据是字段的值,按字段的值进行分类。因此它也是更加关注某个或某几个字段的值为主。
  3. sort
    Sort在文章开始时给出来的实例就是讲DocValues在Sort情场下的应用 ,这里不再赘述了。

2.4 docValuesFormat

As you know, docValues提供了四种Format,分别是Lucene54DocValuesFormat、MemoryDocValuesFormat、DirectDocValuesFormat、SimpleTextDocValuesFormat。接下我们具体来看看这四种格式:

  1. Lucene54DocValuesFormat
    默认的Format,存储在磁盘。跟FieldCache比较像,需要时如果没有就去磁盘里读取,不用时也能释放。
  2. MemoryDocValuesFormat
    它绝对不是如名那些In-Memory,它其实还比较复杂。应该说,它允许你只读一定大小的DocValues驻在内存。同时它支持以FST的格式存储。
  3. DirectDocValuesFormat
    这个关系是Direct,直接。直接存储到磁盘;直接读取出来放在内存。以一个非常简单的结构存储在内存中——数组。它跟MemoryDocValuesFormat最大的一样就是它支持1或0,要么都存;要么都不存。
  4. SimpleText
    Lucene4.0的试验产品。
  • 误区:
    你可能会认为MemoryDocValuesFormat只会存储在内存,而不存储在磁盘。大明可以很负责任的跟你说,真不是这样的。当然你略微想想也觉得不太可能的。
    不管是DirectDocValuesFormat还是MemoryDocValuesFormat都会存盘,后缀名分别为.dvdd.dvdm以及.mdvd.mdvm
  • FST:Finite State Tree
    可以理解为是一种压缩手段,用减少内存使用。

这里主要是介绍DocValues的应用场景,关于DocValues更多底层实现细节请读《Lucene DocValues索引文件详解》


猜你喜欢

转载自blog.csdn.net/zteny/article/details/60633374
今日推荐