剖析Elasticsearch的IndexSorting:一種查詢性能優化利器

摘要: 前言 前兩週寫過一篇《基於Lucene查詢原理分析Elasticsearch的性能》,在最後留了一個彩蛋,說下一篇會介紹一種能夠極大的優化查詢性能的技術。本文就來介紹這種技術——IndexSorting。算法

前言

前兩週寫過一篇《基於Lucene查詢原理分析Elasticsearch的性能》,在最後留了一個彩蛋,說下一篇會介紹一種能夠極大的優化查詢性能的技術。本文就來介紹這種技術——IndexSorting。apache

由於IndexSorting是在ES6.0以後才做爲實驗性的功能加入,相關的介紹資料還比較少,因此大部分人對它不夠了解。另外一方面是,想要理解它爲何可以優化性能、適合哪些場景、內部如何實現、有何反作用等,也須要花一翻功夫。因此本文專門對IndexSorting進行一個介紹,並分析它的做用、實現、適用場景等。若是你的場景能用上IndexSorting,那麼它確定會給你帶來一個巨大的性能提高!數據結構

什麼是IndexSorting?

IndexSorting是ES的新功能

在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

IndexSorting是一種預排序

與查詢時的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能夠優化性能?

提早中斷

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便可,避免了所有掃描,性能甚至能夠提高几個數量級。

IndexSorting提升了數據壓縮率

Lucene中使用了不少的壓縮算法來對數據進行壓縮,壓縮一方面減小了磁盤使用量,另外一方面也減小了查詢時讀取磁盤的數據量和IO次數等,對查詢性能有很大幫助。

Lucene中同時包括行存(Store)與列存(DocValues)的存儲方式,但不論是行存仍是列存,當應用IndexSorting後,相鄰數據的類似度就會越高,也就越利於壓縮。這不只僅是體如今排序字段上,也體如今其餘字段的類似度上。好比時序場景,當按照時間排序後,各個Metrics的值的近似度也會越大。因此IndexSorting能夠提升數據的壓縮率。

IndexSorting是否適合個人場景?

由排序條件決定是否適合

IndexSorting最大的做用就是優化查詢性能,其適用的場景就是可以被其優化的場景,好比說:

  1. 查詢時須要根據某列或者某幾列排序後返回的場景:讓IndexSorting順序跟要查詢的Sort順序一致,讓Lucene可以提早中斷來提高性能。
  2. 不關心結果順序的場景:也能夠按照某列IndexSorting,查詢時也設置按照這一列Sort便可。
  3. 有幾種排序需求的場景:IndexSorting起碼能夠優化一種排序需求,其他的幾種需求能夠考慮是否多建幾個索引,用空間換時間。

也有些場景不能被其優化,好比根據文檔類似度分數排序的場景,這時候很難進行預排序,由於類似分數是每次查詢時纔算出來的。

根據查詢原理看是否能優化

上面提到Lucene會按照docID從小到大的順序選出符合條件的doc,可是有時查詢並非慢在這個篩選過程,而是構造docID列表的過程,這時IndexSorting帶來的優化效果會比較有限。

由於Lucene的查詢原理是比較複雜的,這裏只列舉兩個例子:

  1. 對於字符串進行Range查詢,且Range範圍內有不少符合條件的Term的場景。這個場景下,查詢可能會慢在兩個地方,一個是Range範圍內符合條件的Term很是多,掃描FST耗時很大,另外一個若是這些Term對應的doc數不少,要構造BitSet也會很是耗時。由於利用IndexSorting的提早中斷是發生在BitSet構造好以後,因此並不能優化到這個地方的性能。
  2. 對數字類型在BKD-Tree上進行範圍查找時,由於BKD-Tree裏的docID不是順序排列的,因此並不像倒排鏈同樣能夠順序讀取。若是BKD-Tree上符合條件的docID不少,構造BitSet也很耗時,也不是IndexSorting可以優化到的。

考慮對寫入性能的影響

IndexSorting優化的是查詢性能,由於在寫入時須要對數據進行排序,因此下降了寫性能。若是寫性能是目前的性能瓶頸,或者看重寫性能要高於查詢性能,那麼不適合使用IndexSorting。

IndexSorting是如何實現的?

本文介紹一下IndexSorting的實現細節,這也有助於你們理解它對寫入性能產生的影響。IndexSorting能夠保證在每一個Segment中,數據都是按照設置的方式進行排序的,這要解決兩個問題:

  1. Lucene的Flush操做會生成Segment,這時候生成的Segment如何保證數據有序。
  2. 多個Segment進行合併時如何保證有序。

1. Flush時保證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開啓時的寫入性能?

2. Merge時保證新的Segment數據有序

因爲Flush時Segment已是有序的了,因此在Merge時也就能夠採用很是高效的Merge Sort的方式進行。

總結

IndexSorting是一種可以極大提升查詢效率的技術,它經過預排序和提早中斷大大減小了須要掃描的數據量,並且附帶的優化是能夠提升壓縮率,減小存儲空間。對於查詢時須要按照某列排序的場景,它很是有用,但對於相關性分數排序的場景則沒法經過預排序來優化。IndexSorting的缺點是對寫入性能有影響,主要是體如今Segment的Flush和Merge階段,對於很是看重寫入性能的場景也不適合使用。整體上說,這是一項很是有用也很新的技術,相信它在Lucene和ES中的重要性會愈來愈強,也會有愈來愈多的業務場景受益於這個功能。

參考文檔

  1. https://www.elastic.co/blog/index-sorting-elasticsearch-6-0
  2. https://www.elastic.co/blog/space-savings-a-lesser-known-benefit-of-index-sorting-in-elasticsearch
  3. https://issues.apache.org/jira/browse/LUCENE-7579
  4. https://zhuanlan.zhihu.com/p/35795070
  5. https://zhuanlan.zhihu.com/p/47951652

原文連接 

相關文章
相關標籤/搜索