Elasticsearch 5.x 源碼分析(5)segments merge 流程分析

這兩周主要看了下 Elasticsearch(其實是Lucene)的 segments 的 merge 流程焕盟。事情起因是哼勇,線上的ES有些大索引,其中的segments 個數(shù)幾十個土砂,每個大小100M+乏悄,小 segments 若干龙亲,而遇到問題就是這些大的 segments 不再做 merge 了陕凹,除非強制進行forceMerge 操作震鹉,由于我們第一次ES上線,其實也不清楚這究竟是個問題還是本來 Lucene 就是這樣捆姜,網上找了一些關于ES 或者 Lucene 的 merge 的策略介紹传趾,除了說道大家都了解的一些常規(guī)的參數(shù),如最大size泥技,最大doc 下不會再做merge云云浆兰,就是沒提到到了多少個 segments 之后就不 merge ,接著問了Elasticsearch 圈子的一些人珊豹,也沒有找到非常確定的答案簸呈。查找?guī)滋熨Y料無果,順帶就看看源碼店茶,最終在昨天又瞄了幾眼終于發(fā)現(xiàn)一段算法蜕便,雖無驗證,但應八九不離十贩幻,故記錄分享之轿腺。


Segments

首先還是先重溫一下 Lucene 下的 segments丛楚,對這個比較陌生的可以閱讀三斗大神的這一節(jié)

Lucene產生segments示意圖

我只引用最下面那張圖介紹一下,綠色的就是已經固化的一個個的 segments 文件趣些,不會再更新仿荆,左下角就是當前在內存的 Lucene 維護的查詢可見的仍為持久化的segment,當Elasticsearch 配置的refresh_invterval (默認是1s坏平,可調)到時拢操,這些in in-memory buffer就會推送到OS的文件系統(tǒng)緩存中去,注意這里只是到緩存舶替,很可能OS仍未持久化到文件系統(tǒng)令境,成為一個單獨的 segment 文件,而啥時commit 到文件系統(tǒng)永不丟失坎穿,則由Lucene 的flush 機制保證展父,當Lucene 做完flush 則表明該 segment 真正推送到文件系統(tǒng),此時才會在translog做標記并可以刪除commit之前的translog 了玲昧。

注意這里只是一個簡化描述,據三斗和賴總介紹篮绿,Lucene 仍有很多因素會促使產生一個segment 而不是百分百由Elasticsearch的refresh_interval 決定孵延。這里就不繼續(xù)討論究竟在哪些情況會立即生成一個segment了。

Segment 的merge

詳細信息可看三斗這一節(jié) 這里只引用兩個圖介紹一下

merge前

merge后

從圖上看亲配,Lucene每次會選取一些小的segments 進而merge到一個大的segment尘应,我這里不再贅述流程和策略惶凝,這里只補充一句就是,如果你之前用的scroll查詢犬钢,之前的scroll還是會指向老的segments苍鲜,也就是說老的segments 的引用會知道scroll失效后才會被回收。


Elasticsearch 5.x merge 參數(shù)的變化

在老的Elasticsearch 中玷犹,merge 被認為是一個非常消耗資源的操作混滔,甚至只有一個線程來做這事,并且會影響indexing的request歹颓。在之前的版本里坯屿,merge 操作用的是一類 merge throttle limit這樣的配置來限制各種峰值數(shù)據,如下面這些參數(shù)巍扛,注意這些參數(shù)都已經在5.x 中移除掉了领跛。

  • indices.store.throttle.type
  • indices.store.throttle.max_bytes_per_sec

因為在Elasticsearch 5.x 想采用多線程和動態(tài)調整這種方式來更加智能地去執(zhí)行merge操作。如檢測是否使用SSD硬盤撤奸,應該啟動多少個merge線程等吠昭。
在Elasticsearch 5.x 下,tired merge policy 成為了唯一的merge策略胧瓜。因此下面的參數(shù)同樣也在5.x 下被移除了怎诫。

  • index.merge.policy.type
  • index.merge.policy.min_merge_size
  • index.merge.policy.max_merge_size
  • index.merge.policy.merge_factor
  • index.merge.policy.max_merge_docs
  • index.merge.policy.calibrate_size_by_deletes
  • index.merge.policy.min_merge_docs
  • index.merge.policy.max_merge_docs

如上面說的,ES希望采用一種更智能的方式去調整這些參數(shù)贷痪,達到一個性能的折中幻妓。在5下我們可以配置這些參數(shù):

  • index.merge.policy.expunge_deletes_allowed: 指刪除了的文檔數(shù)在一個segment里占的百分比,默認是10劫拢,大于這個值時肉津,在執(zhí)行expungeDeletes 操作時將會merge這些segments.
  • index.merge.policy.floor_segment: 官網的解釋我沒大看懂,我的個人理解是ES會避免產生很小size的segment舱沧,小于這個閾值的所有的非常小的segment都會做merge直到達到這個floor 的size妹沙,默認是2MB.
  • index.merge.policy.max_merge_at_once: 一次最多只操作多少個segments,默認是10.
  • index.merge.policy.max_merge_at_once_explicit: 顯示調用optimize 操作或者 expungeDeletes時可以操作多少個segments熟吏,默認是30.
  • index.merge.policy.max_merged_segment: 超過多大size的segment不會再做merge距糖,默認是5g.
  • index.merge.policy.segments_per_tier: 每個tier允許的segement 數(shù),注意這個數(shù)要大于上面的at_once數(shù)牵寺,否則這個值會先于最大可操作數(shù)到達悍引,就會立刻做merge,這樣會造成頻繁
  • index.reclaim_deletes_weight: 考慮merge的segment 時刪除文檔數(shù)量多少的權重帽氓,默認即可.
  • index.compund_format: 還不知道干啥用的趣斤,默認即可.

merge 線程調整

Elasticsearch 5 采用了多線程去執(zhí)行merge,可以通過修改index.merge.scheduler.max_thread_count 來動態(tài)調整這個線程數(shù)黎休,默認的話是通過下面公式去計算:

Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2)) 

要注意的是如果你是用HDD而非SSD的磁盤的話浓领,最好是用單線程為妙玉凯。


force merge API

這里有3個參數(shù)可以用

  • max_num_segments 期望merge到多少個segments,1的意思是強行merge到1個segment
  • only_expunge_deletes 只做清理有deleted的segments联贩,即瘦身
  • flush 清理完執(zhí)行一下flush漫仆,默認是true

你可以用下面的URL來執(zhí)行強行的merge

curl -XPOST "http://localhost:9200/library/_forcemerge?max_num_segments=1

代碼介紹

merge的代碼相對比較少,因為基本上ES并沒有做什么東西泪幌,只是采用了多線程盲厌,和直接調用Lucene的API而已,主要看幾個類:

  • MergePolicyConfig.java 專門管理我們輸入的那些參數(shù)
  • MergeScheduler.java 這個類其實就是封裝了一下Lucene的幾個調用
  • ConcurrentMergeScheduler.java 繼承了MergeScheduler.java在上面實現(xiàn)了多線程分工并加上了多線程下的一些限制座菠,如上面配置的那些最大XXX狸眼,最多XXX 之類的。
  • ElasticsearchConcurrentMergeScheduler.java 繼承了ConcurrentMergeScheduler.java 通過配置去控制整個ES實例的merge 流程的運作,還有打印日志等浴滴。
  • TieredMergePolicy.java merge的策略控制類拓萌,之前提到了ES5后只剩下這個唯一的默認的策略控制,所有的選擇升略,打分微王,觸發(fā)merge的策略都在這里定義。

最后發(fā)現(xiàn)問題就出在TieredMergePolicy.java 上品嚣,再次回顧我遇到的問題

事情起因是炕倘,線上的ES有些大索引,其中的segments 個數(shù)幾十個翰撑,每個大小100M+罩旋,小 segments 若干,而遇到問題就是這些大的 segments 不再做 merge 了眶诈,除非強制進行forceMerge 操作涨醋。

然后我們跳到下面這個方法:

public MergeSpecification findMerges(MergeTrigger mergeTrigger, SegmentInfos infos, IndexWriter writer) throws IOException {

<snip> //這里主要拿到總bytes數(shù),segments數(shù)逝撬,踢掉超過閾值的segments

    minSegmentBytes = floorSize(minSegmentBytes);
    // Compute max allowed segs in the index
    long levelSize = minSegmentBytes;
    long bytesLeft = totIndexBytes;
    double allowedSegCount = 0;
    while(true) {
      final double segCountLevel = bytesLeft / (double) levelSize;
      if (segCountLevel < segsPerTier) {
        allowedSegCount += Math.ceil(segCountLevel);
        break;
      }
      allowedSegCount += segsPerTier;
      bytesLeft -= segsPerTier * levelSize;
      levelSize *= maxMergeAtOnce;
    }
    int allowedSegCountInt = (int) allowedSegCount;

<snip>

問題就出在這段算法里了浴骂,tieredPolicy,顧名思義就是梯隊宪潮,階級溯警,等級,就是把所有的segments 分層一個個的階級狡相,ES的設想就是 每個tier 都應該至少包含 segsPerTier 個segments梯轻,這樣從上至下就可以分批的一次每個tier都可以做一輪merge操作,舉個例子谣光,如果我們按默認值floor的size是2MB檩淋,maxMergeAtOnce 使用默認10 的話,那么最低層就應該有 10 個 2MB的segments萄金,做完merge 應該就會產生一個20MB的 segment蟀悦,那么這個20MB應該就是下一個tier, 在這個tier里也應該有至少10個20MB的segments 來等待做merge氧敢,如此類推日戈。
那我就用我遇到的例子來演繹一遍上面的算法,假設我有5個100MB的大segments孙乖,下面就只有少數(shù)幾個的segments浙炼,segsPerTiermaxMergeAtOnce 都采用默認值10。

  1. 第一次while唯袄,segCountLevel = 500/2 = 250 顯然大于10弯屈,所以allowedSegCount = 10bytesLeft = 500 - 2*10 = 480恋拷,levelSize = 2*10 = 20
  2. 第二次while资厉,segCountLevel = 480/20 = 24 還是大于10,所以allowedSegCount = 10 + 10 = 20蔬顾,bytesLeft = 480 - 20*10 = 280宴偿,levelSize = 20*10 = 200
  3. 第三次while,segCountLevel = 280/200 = 1.4 小于10 了诀豁,所以allowedSegCount = 20 + 2 = 22窄刘; 退出

也就是說這次的merge 操作,根據當前segments總的字節(jié)數(shù)推算舷胜,ES應該是被允許最多merge 22 個segments娩践;接著就是去找實際可以merge的總的eligible的segments數(shù)量

// Gather eligible segments for merging, ie segments
     // not already being merged and not already picked (by
     // prior iteration of this loop) for merging:
     final List<SegmentCommitInfo> eligible = new ArrayList<>();
     for(int idx = tooBigCount; idx<infosSorted.size(); idx++) {
       final SegmentCommitInfo info = infosSorted.get(idx);
       if (merging.contains(info)) {
         mergingBytes += size(info, writer);
       } else if (!toBeMerged.contains(info)) {
         eligible.add(info);
       }
     }

     final boolean maxMergeIsRunning = mergingBytes >= maxMergedSegmentBytes;

     if (verbose(writer)) {
       message("  allowedSegmentCount=" + allowedSegCountInt + " vs count=" + infosSorted.size() + " (eligible count=" + eligible.size() + ") tooBigCount=" + tooBigCount, writer);
     }

     if (eligible.size() == 0) {
       return spec;
     }

     if (eligible.size() > allowedSegCountInt) {
      //可以作為預備merge的segments大于允許的數(shù),這輪merge可以做了
      //剩下就是為segments打分烹骨,選出一定數(shù)量的segments來merge
     } else {
      //達不到預期數(shù)量翻伺,不做了
     }

從上面看到,確實如果segments 不夠ES被期望的達到那么多可被merge的segments 數(shù)量的時候展氓,其實ES是不做merge的穆趴。那么就會在一種場景里面出現(xiàn):

當索引達到了比較大時,這時經過了一定時間的merge 完成后遇汞,segments都會比較大未妹,這時如果indexing的頻率相對比較低時,則每輪merge 選擇階段就會得出ES期望這次可以merge的segments數(shù)就會比較大空入,而如果eligible的segments并沒有那么大時络它,則ES就不會進行merge

這就是我得到的結論,還望各ES大神指點是否準確歪赢。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末化戳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌点楼,老刑警劉巖扫尖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掠廓,居然都是意外死亡换怖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門蟀瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沉颂,“玉大人,你說我怎么就攤上這事悦污≈耄” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵切端,是天一觀的道長彻坛。 經常有香客問我,道長帆赢,這世上最難降的妖魔是什么小压? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮椰于,結果婚禮上怠益,老公的妹妹穿的比我還像新娘。我一直安慰自己瘾婿,他們只是感情好蜻牢,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偏陪,像睡著了一般抢呆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笛谦,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天抱虐,我揣著相機與錄音,去河邊找鬼饥脑。 笑死恳邀,一個胖子當著我的面吹牛,可吹牛的內容都是我干的灶轰。 我是一名探鬼主播谣沸,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笋颤!你這毒婦竟也來了乳附?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赋除,沒想到半個月后阱缓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡贤重,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年茬祷,在試婚紗的時候發(fā)現(xiàn)自己被綠了清焕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片并蝗。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秸妥,靈堂內的尸體忽然破棺而出滚停,到底是詐尸還是另有隱情,我是刑警寧澤粥惧,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布键畴,位于F島的核電站,受9級特大地震影響突雪,放射性物質發(fā)生泄漏起惕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一咏删、第九天 我趴在偏房一處隱蔽的房頂上張望惹想。 院中可真熱鬧,春花似錦督函、人聲如沸嘀粱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锋叨。三九已至,卻和暖如春宛篇,著一層夾襖步出監(jiān)牢的瞬間娃磺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工叫倍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留偷卧,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓段标,卻偏偏與公主長得像涯冠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逼庞,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容