Lucene's MergePolicy

Lucene’s MergePolicy

Lucene很多特徵,在我看來跟LSM-Tree的數據庫非常相似,甚至很多問題的解決方式都如出一轍。這裏我想跟大家來聊聊Lucene的Segment合併問題,這個問題同樣發生LSM-Tree數據庫(HBase)。

我們知道我們每次沖刷索引時,Lucene都會生成一個Segment。類似就是每個MemStore的沖刷勢必會產生一個HFile的道理是一樣一樣的。這裏我想跟大家分享的是Lucene的合併策略,前面那長長鋪墊如有興趣的同學可以幫忙看一下,否則直接看Lucene的合併策略部分也無妨。

一、Segment很多會怎麼樣?

我們已經知道所有的索引結構都是樹,因爲樹結構能帶來比較高查找效率,同時對磁盤也更友好。我們也知道樹型結構跟磁盤一樣並不適合做插入寫操作,因此不過是Lucene還是HBase都是在內存上先構建完一個棵後才最終落盤的。方便容易理解和描述,(在理想環境下)我們假定在某種極限條件下,使得每個文檔都會觸發沖刷產生Segment。顯然此時每個Segment文件都有且只有一個文檔,此我們查找效率被完全降級變成線性查找。
如果是每兩個文檔一起纔會被衝成,那麼它的查詢效率將會是$O(logN)$。在這種假設下,我們可以理解成查找效率最終會是$O(K)$(K爲Segment的數量)。

當然經驗告訴我們這是不可能的,因爲Segment內進行查詢也是需要時間的。當文件越來越大,Lucene在Segment裏進行查找時它的IO代價會越來越高。即是需要把索引加載到內存的代價也越來越高了,當然Lucene採用的一個非常先進FST(Finite-State-Transfer,能夠有效減少加載代價,這裏先不展開介紹)。

那我們結論是,當Segment在一個適當大小時,我們查找效率會很高。所以我們不想讓Semgnet文件太多,也不想讓Segment文件太大。

二、所以需要合併

基於以上結論,我們希望總能將那些不太大的Segment合併成另一個不太大的Segment以獲取更高查找效率。不管Lucene還是HBase(或者是其它LSM Tree結構數據庫)都有合併過程,合併主要是有以下兩個大作用。
1. 減少文件的個數提高性能;
2. 清除無效文檔(被標識爲刪除的文檔)。

跟LSM-Tree的數據庫一樣都有合併的問題,而且功能也都雷同。目的就是爲清除無效的數據,同時減少文件數量。

2.1 爲什麼只有追加

因爲磁盤跟數組一樣,刪除和插入都非常不友好,爲了更極致的性能,我們將所有操作變成順序寫。

理論上,Lucene/LSM-Tree並沒有更新和刪除操作。這些操作都是通過特殊的手段來實現,從磁盤的角度看都是寫入。在Lucene/LSM-Tree做一個更新時儘管與原文檔長度完全一樣,也只能將原文檔標記爲刪除,同時再追加一條新記錄。
只因爲他們在方面都有顯著的特點,即是Append only,順序寫入。

因此他的寫性能特別高(線性時間複雜度),但需要消耗額外資源來完成清除和合並的操作。所以說,所有的美好的都是有人代價。在寫時擁有非常優秀的表現的同時需要被後期消耗甚至更多的代價來優化讀的性能。當然合併就是讀優化的一部分,讀優化還有其它很多很多內容。

2.2 Lucene索引合併策略

竟然合併勢在必行,那我們只能寄希望於每次合併都是最有效率的。那麼問題來了,怎麼合併怎麼纔是有效的呢?爲此,出生比較早的Lucene有以下實踐(據悉HBase也走過相當的路)。

Lucene提供如下三大MergePolicy,後面Solr又擴展了一種叫SortingMergePolicy。

  1. LogMergePolicy
    1. LogByteSizeMergePolicy
    2. LogDocMergePolicy
  2. TieredMergePolicy
  3. UpgradeIndexMergePolicy

TieredMergePolicyLucene4.0成爲Lucene的默認合併策略。

老實說最後一種(UpgradedIndexMergePolicy)我也不瞭解它。也沒用過,也鮮有人提及。

2.2.1 LogMergePolicy

這裏先來介紹一下LogMergePolicy吧,這種合併策略非常好理解,她提供一種定長的合併方式。我們只介紹LogByteSizeMergePolicy,因爲LogDocMergePolicy與LogByteSizePolicy的原理一樣。LogByteSizeMergePolicy描述了合併條件的上水位和下水位,默認的上水位是2Gb。即是撿出一堆不超出上限的Segmnet進行合併,且將這一堆Segment分成若干層,默認上限是2GB

在LogMergePolicy的實現上,總能感覺到作者在設計這個策略的時候,已經是假設Segment文件大小基本符合梯形分佈。

實際上LogMergePolicy的實現有些晦澀難懂,不過不用着急,都坐下聽我說。如果Segment文件的大小總能大體符合梯形分佈的,且從左到右按從大到小排列的。然後在這個系列中找到最大的Segment,並把其大小記爲max,然後把max減去一個跨度記爲bottom。同時從右向左遍歷找到第一個Segment文件大小小於等於bottom的Segment,並把其下標記爲upto。然後把從左開始位置start=0,到upto之間的Segment作爲第一層級。然後把start=upto+1,又一次從最右邊開始找到bottom所有Segment,這個區間稱爲第二層級。以此類推至將所有Segment都劃分完成。

然後根據MergeFactor(合併因子,默認爲10)把一個層級拆分成多個合併單元,拆分合並單元的規則是根據start到upto之間的Segment的個數size做如下處理:
1. size < 10,start=upto+1,即是放棄整個Level
2. Size = 10*N,拆分N個合併單元
3. size = 10*N + m,把前10*N拆分N個合併單元,把最後m個Semgent扔掉

值得注意是如果這個合併單元裏有一個Segment越界或者正處於合併狀態或待合併狀態的,這個合併單元會被放棄。

說一千道一萬,我覺得都是廢話。下面用一句話來概括一下這個合併策略:

只能與相鄰的Segment進行合併,不管鄰居們是高大還是矮小,它們最後總會合併在一起。

原本以爲,按Segment編碼排序從左向右選取一定量的Segment進行合併,同時還引入了一個很複雜的分層算法。講道理的話,Segment按文件大小跟Segment編號成反比。若用一個柱體來表示文檔的大小話,柱體最終應該總能形成是一個梯形分佈。然而事實上事與願違,他最終分佈往往是不規則的。即是理想狀態下,segment文件是按梯形分佈的。然而現實非常現實卻是那麼的殘酷,總是能出現小文檔跟大文檔發生合併。

那麼在這個範圍內的索引都會被拉進來發生合併,而且在極端是條件下,幾兆的文件在跟上千兆的文件在合併。這相當於什麼呢,相當於一個1/2Gb的文件在移來移去。這是非常耗資源的過程,而且帶來的效益無乎沒有。

小文檔跟大文檔合併會怎麼樣?

首先是Lucene/LSM-Tree都是Append Only的,所以當出現合併的時候,實際上是把原Segment讀出來重新寫入磁盤變成一個新Segment。

好了,開始“如果”之路。
如果我有兩個Segment,一個有10萬文檔,文件大小爲2Gb;另一個有1個文檔,文件大小爲1Kb。現在來了,此時是不是可以理解爲把大文件重新寫一遍。如果你的Lucene總是了發生這樣的事的話,那你的Lucene的Merge隊列將會被阻塞,你的系統十分耗機器的資源,估計也正處於崩潰的邊緣了。

到這裏我們可以清晰的知道,LogMergePolicy的設計是有缺陷的MergePolicy。

2.2.2 TieredMergePolicy

既然文檔合併是一個不可規避的問題,那我們只能讓每次合併有更大效益。

實際上LogMergePolicy分層就是爲了解決合併單元的Segment大小差異過於懸殊的問題,只是說沒有解決好而已。因此才能引入TieredMergePolicy一個全新合併策略。經過以上的描述其實,不難知道應該怎麼避免這個問題。其最終目的是非常清楚的,就是寧可少發生一些合併,也不要將大文件跟小文件合併。

TieredMergePolicy在每一次撿起時,都會先做一次排序,按文件大小排序。因此TieredMergerPolicy已經打破了必須與相鄰的Segment才能合併的規則,從全局的眼界去挑選更合適的,大小更接近的Segment進行合併。

TieredMergePolicy的實現是挺複雜,也很難懂的。早期Lucene的代碼着實有些天秀的感覺,常常會被代碼組織得非常晦澀。往後的代碼,也能看到很多難懂的代碼被重構了,可讀性越來越高。

最後介紹一下TieredMergePolicy的實現粗節,
1. Segment先按從大到小排序
2. 統計並過濾大於預設最大Segment的一半的Segment
3. 過濾掉正處於合併狀態,或者已經在待合併狀態的Segment
4. 如果一層裏的合併後的總大小超過設定的最大值(默認5Gb),該層被扔掉
5. 對每層侯選集進行打分,依據是合併後的文件大小以及刪除率

TieredMergePolicy總能避免選中大文件出來進行合併,這些大文件一般都是以爲更高的刪除率時,或者系統比較空閒的時候會被合併。由些可見TieredMergePolicy總選擇合併代價最低,且結果往往能撿出性價比更高的Segment進行合併。

三、結語

TieredMergePolicy如果沒有記錯應該是大神Mike帶來,出生於3.x並在4.0時升級爲默認的MergePolicy。據大神Mike表示,TieredMergePolicy帶來8x的性能提升。

雖然說MergePolicy問題,近期並沒有在Lucene的issue中提及了。我也是偶然整理自己的筆記的時候發這個topic,因此整理整理發出來與大家分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章