今天斗胆来试试DocValues,对于DocValues我想大家都不会觉得陌生,同时又不是非常熟悉,就是那种熟悉而又陌生的感觉。
DocValues在LUCENE-3518才引入新特征,初生在Lucene4.0,由Mike(Michael McCandless)提出的。从此Lucene牛逼轰轰了。
接下来一段的话,大神略过。我们再来回顾一下倒排索引的搜索排序过程
我们已经知道倒排表存储了term 对就在的 docIds,也就是说我们可以用它非常高效的找到所有含有查询词的文档得到一个结果集。这个结果集含有满足查询条件的docid(即文档号),这个结果集极有可能非常大。
这里有两个非常重要的件:1. 结果集只有含文档号,不含文档的内容;2. 这个结果集很大,有很多个文档号。
当然一般来我们并不需要整个结果集,只需要按一定条件topK。
- 当一定条件是指按相似度(Similarity得分)排序时,倒排索引依然非常完美。
- 当一定条件是指除相似度排序之外,还需要依赖文档的一个或者多个字段时(即sort=f desc),其实是不是必须读取原文档,然后对应字段拿出来排序呢?答案是肯定。
所以倒排索引很好,但好像并不能解决一切问题。
对应此场景这个优化过程看似并不起眼,实际上在数据比较大的时候效果还是比较显著的。
按正向索引的定义,正向索引就是上面的优化方案,通过文档号直接找到字段值。因此正向索引又称为列存储。
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的原理进行深度的剖析,根据官方文档和源码进行深入了解和解读。
官方文档都有提到,DocValues字段是一个面向列存储的字段,一个Segment只有一个DocValues文件。也就是被DocValues标记的字段在建索引时会额外存储文件到值的映射关系,存储这个映射的文件叫DocValues data,简称**.dvd**。对应的元数据文件叫**.dvm**:DocValues Metadata。
这里metadata相当于Index文件,用来存储数据的索引,也就是存储数据文件每条记录起始位置以及记录的长度,同时存储数据文件的数据格式等信息(即是元信息)。
所谓面向列存储,相信大家都不会陌生了,市场上也有好多成熟的列存储框架诸如:Parquet、ORC等。
我们先看一下Parquet文档给出来的结构图,非常清晰
这是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的视角一样。
- consumer对于write完成持久过程,将缓存在writer的内容写入索引文件。
- producer对于reader完成反序列化过程,把索引入件读到Reader缓存。
这个结论有些草率,它并不是一般结论。对于writer与consumer成对出现的满足这个规律,至少DocValues是这样的。
如此机智的你一定会想问,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.
- If the field is single-valued (i.e., multi-valued is false), Lucene will use the SORTED type.
- If the field is multi-valued, Lucene will use the SORTED_SET type.
- Any Trie* numeric fields, date fields and EnumField.
- If the field is single-valued (i.e., multi-valued is false), Lucene will use the NUMERIC type.
- 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适配情况应该如此:
multiValue="true"
的情况因此docValues不支持TextField类型
对DocValues用法的话,可以直接参考Solr User Guider。当然对于应用情场也是可以直接参考Solr User Guider的。这里我们也不能啥都说是吧,主要还是关注一下应用情场吧。
DocValues解决是从文档号到值的映射关系的问题,前面也谈到了什么情况下需要用到正向索引。即是在大量结果集里通过少量字段进行大规模筛选过滤候选文档时,DocValues可以显著减少IO时间和降低运算量。
那么问题来,就是“你说的我都懂,可我就不知道那些操作属于这种情呢?”。官方文档说,Facet/Group By/Sort/部分Function Search。
其实也不难理解,我们一个一个来看:
As you know, docValues提供了四种Format,分别是Lucene54DocValuesFormat、MemoryDocValuesFormat、DirectDocValuesFormat、SimpleTextDocValuesFormat。接下我们具体来看看这四种格式:
- 误区:
你可能会认为MemoryDocValuesFormat只会存储在内存,而不存储在磁盘。大明可以很负责任的跟你说,真不是这样的。当然你略微想想也觉得不太可能的。
不管是DirectDocValuesFormat还是MemoryDocValuesFormat都会存盘,后缀名分别为.dvdd
和.dvdm
以及.mdvd
和.mdvm
。- FST:Finite State Tree
可以理解为是一种压缩手段,用减少内存使用。
这里主要是介绍DocValues的应用场景,关于DocValues更多底层实现细节请读《Lucene DocValues索引文件详解》。