摘要: 前言 前兩週寫過一篇《基於Lucene查詢原理分析Elasticsearch的性能》,在最後留了一個彩蛋,說下一篇會介紹一種能夠極大的優化查詢性能的技術。本文就來介紹這種技術——IndexSorting。算法
前兩週寫過一篇《基於Lucene查詢原理分析Elasticsearch的性能》,在最後留了一個彩蛋,說下一篇會介紹一種能夠極大的優化查詢性能的技術。本文就來介紹這種技術——IndexSorting。apache
由於IndexSorting是在ES6.0以後才做爲實驗性的功能加入,相關的介紹資料還比較少,因此大部分人對它不夠了解。另外一方面是,想要理解它爲何可以優化性能、適合哪些場景、內部如何實現、有何反作用等,也須要花一翻功夫。因此本文專門對IndexSorting進行一個介紹,並分析它的做用、實現、適用場景等。若是你的場景能用上IndexSorting,那麼它確定會給你帶來一個巨大的性能提高!數據結構
在Elasticsearch中,IndexSorting是一個很新的功能,6.0版本才引入,而且標記這個功能是Beta版(6.5版本可能會去掉Beta標記)。less
在Lucene中,IndexSorting其實已經發展了一段時間。最先在10年,Lucene提供了一個IndexSorter的工具,做爲一個離線工具能夠對Index數據排序後生成一個新的Index。後來13年加入了SortingMergePolicy,在Segment進行merge的時候能夠生成排好序的新Segment,在17年又加入了Sorting on flushed segment的功能,在Segment最初生成時就進行排序。另外一方面是Lucene在查詢時也作了不少優化,若是有IndexSorting,不少地方作了提早中斷,後面會講提早中斷對查詢性能的巨大做用。通過幾回Lucene的改進和優化,IndexSorting這個功能也終於被集成進Elasticsearch。elasticsearch
與查詢時的Sort不一樣,IndexSorting是一種預排序,即數據預先按照某種方式進行排序,它是Index的一個設置,不可更改。你們知道,Elasticsearch的底層是Lucene,Lucene中是以Segment爲單位進行查詢的,這裏說的IndexSorting對數據進行預排序也是在每一個Segment內有序的。工具
一個Segment中的每一個文檔,都會被分配一個docID,docID從0開始,順序分配。在沒有IndexSorting時,docID是按照文檔寫入的順序進行分配的,在設置了IndexSorting以後,docID的順序就與IndexSorting的順序一致。性能
舉個例子來講,假如文檔中有一列爲Timestamp,咱們在IndexSorting中設置按照Timestamp逆序排序,那麼在一個Segment內,docID越小,對應的文檔的Timestamp越大,即按照Timestamp從大到小的順序分配docID。優化
IndexSorting可以優化性能,主要就是靠四個字,「提早中斷」。可是提早中斷是有條件的,須要查詢的Sort順序與IndexSorting的順序相同,而且不須要獲取符合條件的記錄總數(TotalHits)。spa
Lucene中的倒排鏈都是按照docID從小到大的順序排列的,在進行組合條件查詢時,也是按照docID從小到大的順序選出符合條件的doc。那麼當查詢時的Sort順序與IndexSorting的順序相同時,會發生什麼呢?blog
好比查詢時但願按照Timestamp降序排序後返回100條結果,在Lucene中進行查詢時,發現docID對應的doc順序也恰好是Timestamp降序排序的,那麼查詢到前100個符合條件的結果便可返回,這100個必定也是Timestamp最大的100個,這就作到了提早中斷。
提早中斷能夠極大的提高查詢性能,特別是當一個查詢條件命中的文檔數量很是多的時候。在沒有IndexSorting時,必須把全部符合條件的文檔的docID掃描一遍,而且讀取這些doc的一些字段來排序,選出符合條件的doc。有了IndexSorting以後,只須要選出前Top個doc便可,避免了所有掃描,性能甚至能夠提高几個數量級。
Lucene中使用了不少的壓縮算法來對數據進行壓縮,壓縮一方面減小了磁盤使用量,另外一方面也減小了查詢時讀取磁盤的數據量和IO次數等,對查詢性能有很大幫助。
Lucene中同時包括行存(Store)與列存(DocValues)的存儲方式,但不論是行存仍是列存,當應用IndexSorting後,相鄰數據的類似度就會越高,也就越利於壓縮。這不只僅是體如今排序字段上,也體如今其餘字段的類似度上。好比時序場景,當按照時間排序後,各個Metrics的值的近似度也會越大。因此IndexSorting能夠提升數據的壓縮率。
IndexSorting最大的做用就是優化查詢性能,其適用的場景就是可以被其優化的場景,好比說:
也有些場景不能被其優化,好比根據文檔類似度分數排序的場景,這時候很難進行預排序,由於類似分數是每次查詢時纔算出來的。
上面提到Lucene會按照docID從小到大的順序選出符合條件的doc,可是有時查詢並非慢在這個篩選過程,而是構造docID列表的過程,這時IndexSorting帶來的優化效果會比較有限。
由於Lucene的查詢原理是比較複雜的,這裏只列舉兩個例子:
IndexSorting優化的是查詢性能,由於在寫入時須要對數據進行排序,因此下降了寫性能。若是寫性能是目前的性能瓶頸,或者看重寫性能要高於查詢性能,那麼不適合使用IndexSorting。
本文介紹一下IndexSorting的實現細節,這也有助於你們理解它對寫入性能產生的影響。IndexSorting能夠保證在每一個Segment中,數據都是按照設置的方式進行排序的,這要解決兩個問題:
你們知道,數據寫入Lucene後,並非當即可查的,要生成Segment以後才能被查到。爲了保證近實時的查詢,ES會每隔一秒進行一次Refresh,Refresh就會調用到Lucene的Flush生成新的Segment。額外說的一點是,Lucene的Flush不一樣於ES的Flush,ES的Flush保證數據落盤,調用的是Lucene的commit,裏面會調用fsync,這裏的關係值得額外寫一篇文章來講清楚。
咱們須要先知道Flush前數據是一個什麼樣的狀態,才能知道Flush時如何對這些數據排序。每一個doc寫入進來以後,按照寫入順序被分配一個docID,而後被IndexingChain處理,依次要對invert index、store fields、doc values和point values進行處理,有些數據會直接寫到文件裏,主要是store field和term vector,其餘的數據會放到memory buffer中。
在Flush時,首先根據設定的列排序,這個排序能夠利用內存中的doc values,排序以後獲得老的docID到新docID的映射,由於以前docID是按照寫入順序生成的,如今重排後,生成的是新的排列。若是排序後與原來順序徹底一致,那麼什麼都不作,跟以前流程同樣進行Flush。
若是排序後順序發生變化,如何排序呢?對於已經寫到文件中的數據,好比store field和term vector,須要從文件中讀出來,從新排列後再寫到一個新文件裏,原來的文件就至關於一個臨時文件。對於內存中的數據結構,直接在內存中重排後寫到文件中。
相比沒有IndexSorting時,對性能影響比較大的一塊就是store field的重排,由於這部分須要從文件中讀出再寫回,而其餘部分都是內存操做,性能影響稍小一些。這裏咱們也能夠作一些思考,若是將store field和term vector這類數據也buffer在內存中,是否能夠提高IndexSorting開啓時的寫入性能?
因爲Flush時Segment已是有序的了,因此在Merge時也就能夠採用很是高效的Merge Sort的方式進行。
IndexSorting是一種可以極大提升查詢效率的技術,它經過預排序和提早中斷大大減小了須要掃描的數據量,並且附帶的優化是能夠提升壓縮率,減小存儲空間。對於查詢時須要按照某列排序的場景,它很是有用,但對於相關性分數排序的場景則沒法經過預排序來優化。IndexSorting的缺點是對寫入性能有影響,主要是體如今Segment的Flush和Merge階段,對於很是看重寫入性能的場景也不適合使用。整體上說,這是一項很是有用也很新的技術,相信它在Lucene和ES中的重要性會愈來愈強,也會有愈來愈多的業務場景受益於這個功能。