lucene索引文件大小優化小結 地理空間距離計算優化 lucene join解決父子關係索引 lucene字典實現原理 排序學習實踐 lucene如何經過docId快速查找field字段以及最近距

http://www.cnblogs.com/LBSer/p/4068864.html

      隨着業務快速發展,基於lucene的索引文件zip壓縮後也接近了GB量級,而保持索引文件大小爲一個能夠接受的範圍很是有必要,不只能夠提升索引傳輸、讀取速度,還能提升索引cache效率(lucene打開索引文件的時候每每會進行緩存,好比MMapDirectory經過內存映射方式進行緩存)。html

      如何下降咱們的索引文件大小呢?本文進行了一些嘗試,下文將一一介紹。java

1 數值數據類型索引優化

1.1 數值類型索引問題

        lucene本質上是一個全文檢索引擎而非傳統的數據庫系統,它基於倒排索引,很是適合處理文本,而處理數值類型卻不是強項。數據庫

        舉個應用場景,假設咱們倒排存儲的是商家,每一個商家都有人均消費,用戶想查詢範圍在500~1000這一價格區間內的商家緩存

       一種簡單直接的想法就是,將商家人均消費當作字符串寫入倒排(如圖所示),在進行區間查詢時:1)遍歷價格分詞表,將落在此區間範圍內的倒排id記錄表找出來;2)合併倒排id記錄表。這裏兩個步驟都存在性能問題:1)遍歷價格分詞表,比較暴力,並且經過term查找倒排id記錄表次數過多,性能很是差,在lucene裏查詢次數過多,可能會拋出Too Many Boolean Clause的Exception。2)合併倒排id記錄表很是耗時,說白了這些倒排id記錄表都在磁盤裏。數據結構

       固然還有種思路就是將其數字長度補齊,假設全部商家的人均消費在[0,10000]這一區間內,咱們存儲1時寫到倒排裏就是00001(補齊爲5位),因爲分詞表會按照字符串排序好,所以咱們沒必要遍歷價格分詞表,經過二分查找能快速找到在某一區間範圍內的倒排id記錄表,但這裏一樣未能解決查詢次數過多、合併倒排id記錄表次數過多的問題。此外怎樣補齊也是問題,補齊太多浪費空間,補齊太少存儲不了太大範圍值。函數

1.2  lucene解決方法

       爲解決這一問題, Schindler和 Diepenbroek提出了基於trie的解決方法,此方法08年發表在 Computers & Geosciences (地理信息科學sci期刊,影響因子1.9),也被lucene 2.9以後版本採用。( Schindler, U, Diepenbroek, M, 2008. Generic XML-based Framework for Metadata Portals. Computers & Geosciences 34 (12),論文:http://epic.awi.de/17813/1/Sch2007br.pdfpost

       簡單來講,整數423不是直接寫入倒排,而是分割成幾段寫入倒排,以十進制分割爲例,423將被分割爲42三、4二、4這三個term寫入, 本質上這些term造成了trie樹(如圖所示)。性能

       如何查詢呢?假設咱們要查詢[422, 642]這一區間範圍的doc,首先在樹的最底層找到第一個比422大的值,即423,以後查找423的右兄弟節點,發現沒有便找其父節點的右兄弟(找到44),對於642也是,找其左兄弟節點(641),以後找父節點的左兄弟(63),一直找到二者的公共節點,最終找出42三、4四、五、6三、64一、642這6個term便可。經過這種方法,原先須要查詢42三、44五、44六、44八、52一、52二、63二、63三、63四、64一、642這11次term對應的倒排id列表,併合並這11個term對應的倒排id列表,如今僅須要查詢42三、4四、五、6三、64一、642這6個term對應的倒排id列表併合並,大大下降了查詢次數以及合併次數,尤爲是查詢區間範圍較大時效果更爲明顯。學習

       這種優化方法本質上是一種以空間換時間的方法,能夠看到term數目將增大許多。優化

 

       在實際操做中,lucene將數字轉換成2進制來處理,並且實際上這顆trie樹也無需保存數據結構,傳統trie一個節點會有指向孩子節點的指針同時會有指向父節點的指針,而在這裏只要知道一個節點,其父節點、右兄弟節點均可以經過計算獲得。此外lucene也提供了precisionstep這一字段用於設置分割長度,默認狀況下int、double、float等數字類型precisionstep爲4,就是按4位二進制進行分割。precisionstep長度設置得越短,分割的term越多,大範圍查詢速度也越快,precisionstep設置得越長,極端狀況下設置爲無窮大,那麼不會進行trie分割,範圍查詢也沒有優化效果,precisionstep長度須要結合自身業務進行優化。

1.3 索引文件大小優化方案

        咱們的應用中不少field都是數值類型,好比id、avescore(評價分)、price(價格)等等,可是用於區間範圍查詢的數值類型很是少,大部分都是直接查詢或者爲進行排序使用。

        所以優化方法很是簡單,將不須要使用範圍查詢的數字字段設置precisionstep爲Intger.max,這樣數字寫入倒排僅存一個term,能極大下降term數量。

 1 public final class CustomFieldType {
 2     public static final FieldType INT_TYPE_NOT_STORED_NO_TIRE = new FieldType();
 3     static {
 4         INT_TYPE_NOT_STORED_NO_TIRE.setIndexed(true);
 5         INT_TYPE_NOT_STORED_NO_TIRE.setTokenized(true);
 6         INT_TYPE_NOT_STORED_NO_TIRE.setOmitNorms(true);
 7         INT_TYPE_NOT_STORED_NO_TIRE.setIndexOptions(FieldInfo.IndexOptions.DOCS_ONLY);
 8         INT_TYPE_NOT_STORED_NO_TIRE.setNumericType(FieldType.NumericType.INT);
 9         INT_TYPE_NOT_STORED_NO_TIRE.setNumericPrecisionStep(Integer.MAX_VALUE);
10         INT_TYPE_NOT_STORED_NO_TIRE.freeze();
11     }
12 }
doc.add(new IntField("price", price, CustomFieldType.INT_TYPE_NOT_STORED_NO_TIRE));//人均消費

1.4 效果

      優化以後效果明顯,索引壓縮包大小直接減小了一倍。

2 空間數據類型索引優化

.1 地理數據索引問題

       仍是同樣的話,lucene基於倒排索引,很是適合文本,而對於空間類型數據卻不是強項。

       舉個應用場景,每個商家都有惟一的經緯度座標(x, y),用戶想篩選附近5公里的商家。

       一種直觀的想法是將經度x、維度y分別當作兩個數值類型字段寫到倒排裏,而後查詢的時候遍歷全部的商家,計算與用戶的距離,並保留小於5公里的商家。這種方法缺點很明顯:1)須要遍歷全部的商家,很是暴力;2)此外球面距離計算非涉及到大量的三角函數計算,效率較低(博主研發了一種快速距離計算方法,能提升至少10倍計算速度:地理空間距離計算優化)。

       簡單的優化方法使用矩形框對這些商家進行過濾,以後對過濾後的商家進行距離計算,保留小於5公里的商家,這種方法儘管極大下降了計算量,但仍是須要遍歷全部的商家。

2.2  lucene解決方法

         lucene採用geohash的方法對經緯度進行編碼(geohash介紹參見:GeoHash)。簡單描述下,geohash對空間不斷進行劃分並對每個劃分子空間進行編碼,好比咱們整個北京地區被編碼爲「w」,那麼再對北京一分爲4,某一子空間編碼爲「WX」,對「WX」子空間再進行劃分,對各個子空間再進行標識,例如「WX4」(簡單能夠這麼理解)。

         那麼一個經緯度(x,y)怎樣寫入到倒排索引呢?假設某一經緯度落在「WX4」子空間內,那麼經緯度將以「W」、「WX」、「WX4」這三個term寫入到倒排。

         如何進行附近查詢呢?首先將咱們附近5km劃分一個個格子,每一個格子有geohash的編碼,將這些編碼當作查詢term,去倒排查詢便可,好比附近5km的geohash格子對應的編碼是「WX4」,那麼直接就能將落在此空間範圍的商家找出。

2.3 索引文件大小優化方案

       上述方法本質上也是一種以空間換時間的方法,好比一個經緯度(x,y),只有兩個字段,可是以geohash進行編碼將產生許多term並寫入倒排。

       lucene默認最長的geohash長度爲24,也就是一個經緯度將以24個字符串的形式來寫入到倒排中。最初採用的geohash長度爲11,但實際上針對咱們的需求,geohash長度爲9的時候已經足夠知足咱們的需求(geohash長度爲9大約表明了5*4米的格子)。

      下表表示geohash長度對應的精度,摘自維基百科:http://en.wikipedia.org/wiki/Geohash

geohash length
lat bits
lng bits
lat error
lng error
km error
1 2 3 ±23 ±23 ±2500
2 5 5 ± 2.8 ± 5.6 ±630
3 7 8 ± 0.70 ± 0.7 ±78
4 10 10 ± 0.087 ± 0.18 ±20
5 12 13 ± 0.022 ± 0.022 ±2.4
6 15 15 ± 0.0027 ± 0.0055 ±0.61
7 17 18 ±0.00068 ±0.00068 ±0.076
8 20 20 ±0.000085 ±0.00017 ±0.019
1 private void spatialInit() {
2         this.ctx = SpatialContext.GEO; // 選擇geo表示經緯度座標,會按照球面計算距離,不然是平面歐式距離
3         int maxLevels = 9; // geohash長度爲9表示5*5米的格子,長度過長會形成查詢匹配開銷
4         SpatialPrefixTree grid = new GeohashPrefixTree(ctx, maxLevels); // geohash字符串匹配樹
5         this.strategy = new RecursivePrefixTreeStrategy(grid, "poi"); // 遞歸匹配
6     }

 

2.4 效果

      此優化效果結果未作記錄,不過經緯度geohash編碼佔據了term數量的25%,而咱們又將geohash長度從11減小到9(下降18%),至關於整個term數量下降了25%*18%=4.5%。

3 只索引不存儲

       上面兩種方法本質上經過減小term數量來減小索引文件大小,下面的方法走的是另外一種方式。       

       從lucene查出一堆docid以後,須要經過docid找出相應的document,並找出裏面一些須要的字段,例如id,人均消費等等,而後返回給客戶端。但實際上咱們只須要獲取id,經過這些id再去請求DB/Cache獲取額外的字段。

       所以優化方法是隻存儲id等必須的字段,對於大部分字段咱們只索引而不存儲,經過這種方法,索引壓縮文件下降了10%左右。

 1 doc.add(new StringField("price", each, Field.Store.NO)); 

4 小結

     本文基於lucene的一些基礎原理以及自身業務,對索引文件大小進行了優化,使得索引文件大小降低了一半多。

 

 

檢索實踐文章系列:

lucene join解決父子關係索引

lucene字典實現原理

排序學習實踐

lucene如何經過docId快速查找field字段以及最近距離等信息?

相關文章
相關標籤/搜索