天天看點

Lucene DocValues詳解

今天鬥膽來試試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文檔給出來的結構圖,非常清晰

Lucene DocValues詳解

這是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索引檔案詳解》。

繼續閱讀