Lucene的數值索引以及范圍查詢


對文本搜索引擎的倒排索引(數據結構和算法)、評分系統、分詞系統都清楚掌握之后,本人對數值索引和搜索一直有很大的興趣,最近對Lucene對數值索引和范圍搜索做了些學習,並將主要內容整理如下:

1. Lucene不直接支持數值(以及范圍)的搜索,數值必須轉換為字符(串);

2. Lucene搜索數值的初步方案;

3. Lucene如何索引數值,並支持范圍查詢。

 

1. Lucene不直接支持數值搜索

Lucene不直接支持數值(以及范圍)的搜索,數值必須轉換為字符(串)——這是由倒排索引這個核心所決定,lucene要求term按照字典序(lexicographic sortable)排列。如果只是簡單的將數值轉為字符串,會帶來很多的問題:

 

2. Lucene搜索數值的初步方案

2.1 如直接保存11,24,3,50,按照字典序查詢范圍[24,50],會將3一起帶出來。這個問題有個簡單的解決方案,就是將字符串補全成定長的串,如000011,000024,000003,000050。這樣就能解決[000024,000050]這樣的字符范圍查詢。

2.2 建立索引的時候,term按照數字順序排序,上面的例子以3,11,24,50,搜索也能正確。

顯而易見,上述方案有“硬傷”:

 2.1方案的問題是,固定多少位難以控制,補的位數多則浪費空間,少則存儲的數值范圍有限;

 2.2方案的問題是,對范圍[24,50]查詢,必須要展開成25,26...50,這樣Boolean query查詢效率會低到無法接受。

 

3. Lucene如何索引數值,並支持范圍查詢

  首先可以把數值轉換成字符串,且保持順序。也就是說如果 number1 < number2 ,那么transform(number) < transform(number)。transform就是把數值轉成字符串的函數,如果拿數學術語來說,transform就是單調的。

  *注意, 數字做索引時, 只能是同一類型, 例如不可能是同一個field, 里面有int, 又有float的.

 

 3.1 Lucene 對NumericField建索引的時候,首先把Numeric Value轉成 Lexicographic Sortable Binary然后根據某個步長(Precision Step 后面詳說)不斷右移然后轉換成 Lexicographic Sortable String建索引,本質上相當於建了一個Trie。

  怎么把numeric value轉成  Lexicographic Sortable Binary 所有的Byte的詞典順序就是Numeric順序。

  對於Long 二進制表示方式 http://en.wikipedia.org/wiki/Two's_complement

  最高位是符號位0表示正數 1表示負數。對於正數來說低63位越大這個數越大,對於負數來說也是低63位越大(0xFFFFFFFFFFFFFFFF是-1,最大的負整數)這個數越大。所以只要把符號位取反Long就可以按字節映射成一個 Lexicographic Sortable Binary了。

 對於Double 二進制表示方式 http://en.wikipedia.org/wiki/Binary64

How <wbr>Lucene <wbr>NumericRangeQuery <wbr>works

The real value assumed by a given 64-bit double-precision datum with a given biased exponent  and a 52-bit fraction is

How <wbr>Lucene <wbr>NumericRangeQuery <wbr>works

對於正Double來說低63位越大這個數越大,對於負Double來說低63位越大這個數越小。負數情況和Long是相反的,因此對於小於0的Double把低63位取反,然后和Long相同再把符號位取反,Double就可以按字節映射成一個 Lexicographic Sortable Binary了。

對於Int和Float 32位的類型一樣道理,就不贅述了。

 

 3.2 利用Trie的性質把RangeQuery分解成盡量少TermQuery,然后用這些TermQuery做搜索就可以了

原理就是Shift從0開始以precisionStep為步長遞增,對每一個Shift試圖找到最多兩個子Range:Lower和Upper,然后中間的Range繼續遞歸直到break發生,這時的Range成為Center Range。當Shift=n時,對於split出來的Range滿足把minBound的低Shift位全部置0和把maxBound的低Shift位全部置1后之間的所有數值都在要查詢的Range中。基本思想和樹狀數組類似。

 

看例子更容易明白比如[1, 10000]這個Range,通過splitRange出來的Range:

Shift: 0  

Lower: [0x1,0xF],  表示從1到15

Upper: [0x2710,0x2710] 表示10000到10000

Shift: 4

Lower:[0x10, 0xF0]   表示從16(0x10)到255(0xFF) 

Upper:[0x2700, 0x2700]  表示從9984(0x2700)到 9999(0x270F)

Shift: 8

Lower: [0x100,0xF00]  表示從256(0x100)到 4095(0xFFF)

Upper: [0x2000,0x2600] 表示從8192(0x2000)到9983(0x26FF)

Shift: 12

Center: [0x1000, 0x1000] 表示從4096(0x1000)到8191(0x1FFF)

一共7個Range最后一個Range是Center Range, 這7個Range也正好覆蓋了[1,10000]

 

addRange中會對每個split出來的Long Range的minBound和maxBoud右移Shift位然后轉成Lexicographic Sortable String,最后和建索引時一樣在前面加一個Byte表示Shift。因為Shift是以precisionStep為步長遞增的,所以splitRange出來的多個Lexicographic Sortable String Range是遞增的(Pair順序比較)。這樣查找所有屬於這些Range中的Term,只需要對這個field一直seek forward,不需要seek backward。

 

對於上面的例子,這7個Range轉換成Lexicographic Sortable String, 然后用這些Range去查找所有屬於這些Range范圍內的Term。

比如shift: 8

Lower: [0x100,0xF00]  表示從256(0x100)到 4095(0xFFF)

0x100,最高位變成1  成為 0x80,00,00,00,00,00,01,00  然后右移8位變成 0x80,00,00,00,00,00,01 然后每7個bit變成一個Byte成為

0x40, 00, 00, 00, 00, 00, 00,01

0xF00 同理變成0x40, 00, 00, 00, 00, 00, 00,0F。

在最前面加一個Byte表示Shift那么最終的Lexicographic Sortable String

0x100  -> 0x28,40, 00, 00, 00, 00, 00, 00,01

0xF00  -> 0x28,40, 00, 00, 00, 00, 00, 00,0F

第一個Byte 0x28表示Shift為8,0x20是偏移量,區分不同數值類型。

這樣如果要查找[256, 4095]的數值共有3840個,那么只需要查找15個Term  

 0x28,40, 00, 00, 00, 00, 00, 00,01 ~  0x28,40, 00, 00, 00, 00, 00, 00,0F

整體來看[0, 10000]之間共1000個數值,最多需要查找的Term數量是55個。

[0x1,0xF]               15 

[0x2710,0x2710]         1 

[0x10, 0xF0]             15

[0x2700, 0x2700]         1

[0x100,0xF00]           15 

[0x2000,0x2600]         7

[0x1000, 0x1000]         1

如果不做Trie樹,那么需要最多遍歷查找10000個Term。

 

理論上對於precisionStep=4時一個Range最多需要查找多少個Term?

根據splitRange可以看出除了最后一次Shift,前面的每次Shift最多產生兩個Range(Lower 和 Upper),最后一個Shift產生的是Center Range。

64位的數字Value最多Shift  64/4=16次。 所以最多有Lower和Upper最多各15個Range, Center 1個Range,每個Range最多覆蓋15個Term。

為什么不是16個Term?16個Term的話,這個Range的存在是沒有意義可以進位到下一個Shift。

只有一種情況是特殊的就是無法進位的時候,比如Range是[Long.MIN_VALUE, Long.MAX_VALUE]  只得到一個Center Range在Shift=60時,覆蓋了16個Term的。

所以理論上對precisionStep=4,最多需要查找的Term   31個Range * 15個Term/Range = 465

更一般的結論

  n = [ (bitsPerValue/precisionStep - 1) * (2^precisionStep - 1 ) * 2 ] + (2^precisionStep - 1 )

precisionStep=8, n=3825

precisionStep=2, n=189

顯然precisionStep越小n越小,但是precisionStep越小意味着對每個Field需要index的Term越多,對64位的數值需要index的Term是64/precisionStep。

 

以上主要討論了LongField的搜索,對於DoubleField只是需要做一步處理就是對於小於0的Double,低63位取反,接下來和LongField完全相同流程。對於Int和Float只是數值類型從64位變成32位了,其余的都一樣。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM