Elasticsearch搜索引擎:ES的segment段合并原理
在講 segment 之前,我們先用一張圖了解下 ES 的整體存儲架構圖,方便后面內容的理解:
?
一、segment文件的合并流程:
當我們往 ElasticSearch 寫入數據時,數據是先寫入 memory buffer,然后定時(默認每隔1s)將 memory buffer 中的數據寫入一個新的 segment 文件中,并進入 Filesystem cache(同時清空 memory buffer),這個過程就叫做 refresh;每個 Segment 事實上是一些倒排索引的集合, 只有經歷了 refresh 操作之后,數據才能變成可檢索的。
ElasticSearch 每次 refresh 一次都會生成一個新的 segment 文件,這樣下來 segment 文件會越來越多。那這樣會導致什么問題呢?因為每一個 segment 都會占用文件句柄、內存、cpu資源,更加重要的是,每個搜索請求都必須訪問每一個segment,這就意味著存在的 segment 越多,搜索請求就會變的更慢。
每個 segment 是一個包含正排(空間占比90~95%)+ 倒排(空間占比5~10%)的完整索引文件,每次搜索請求會將所有 segment 中的倒排索引部分加載到內存,進行查詢和打分,然后將命中的文檔號拿到正排中召回完整數據記錄。如果不對segment做配置,就會導致查詢性能下降
那么 ElasticSearch 是如何解決這個問題呢? ElasticSearch 有一個后臺進程專門負責 segment 的合并,定期執行 merge 操作,將多個小 segment 文件合并成一個 segment,在合并時被標識為 deleted 的 doc(或被更新文檔的舊版本)不會被寫入到新的 segment 中。合并完成后,然后將新的 segment 文件 flush 寫入磁盤;然后創建一個新的 commit point 文件,標識所有新的 segment 文件,并排除掉舊的 segement 和已經被合并的小 segment;然后打開新 segment 文件用于搜索使用,等所有的檢索請求都從小的 segment 轉到 大 segment 上以后,刪除舊的 segment 文件,這時候,索引里 segment 數量就下降了。如下面兩張圖:
所有的過程都不需要我們干涉,es會自動在索引和搜索的過程中完成,合并的segment可以是磁盤上已經commit過的索引,也可以在內存中還未commit的segment:合并的過程中,不會打斷當前的索引和搜索功能。
?
二、segment 的 merge 對性能的影響:
segment 合并的過程,需要先讀取小的 segment,歸并計算,再寫一遍 segment,最后還要保證刷到磁盤。可以說,合并大的 segment 需要消耗大量的 I/O 和 CPU 資源,同時也會對搜索性能造成影響。所以 Elasticsearch 在默認情況下會對合并線程進行資源限制,確保它不會對搜索性能造成太大影響。
默認情況下,歸并線程的限速配置 indices.store.throttle.max_bytes_per_sec 是 20MB。對于寫入量較大,磁盤轉速較高,甚至使用 SSD 盤的服務器來說,這個限速是明顯過低的。對于 ELK Stack 應用,建議可以適當調大到 100MB或者更高。設置方式如下:
PUT /_cluster/settings {"persistent" : {"indices.store.throttle.max_bytes_per_sec" : "100mb"} }或者不限制:
PUT /_cluster/settings {"transient" : {"indices.store.throttle.type" : "none" } }?
三、手動強制合并 segment:
ES 的 API 也提供了命令來支持強制合并 segment,即 optimize 命令,它可以強制一個分片 shard 合并成 max_num_segments 參數指定的段數量,一個索引它的segment數量越少,它的搜索性能就越高,通常會optimize 成一個 segment。
但需要注意的是,optimize 命令是沒有限制資源的,也就是你系統有多少IO資源就會使用多少IO資源,這樣可能導致一段時間內搜索沒有任何響應,所以,optimize命令不要用在一個頻繁更新的索引上面,針對頻繁更新的索引es默認的合并進程就是最優的策略。如果你計劃要 optimize 一個超大的索引,你應該使用 shard allocation(分片分配)功能將這份索引給移動到一個指定的 node 機器上,以確保合并操作不會影響其他的業務或者es本身的性能。
但是在特定場景下,optimize 也頗有益處,比如在一個靜態索引上(即索引沒有寫入操作只有查詢操作)是非常適合用optimize來優化的。比如日志的場景下,日志基本都是按天,周,或者月來索引的,舊索引實質上是只讀的,只要過了今天、這周或這個月就基本沒有寫入操作了,這個時候我們就可以通過 optimize 命令,來強制合并每個shard上索引只有一個segment,這樣既可以節省資源,也可以大大提升查詢性能。
optimize 的 API 如下:
POST /logstash-2014-10/_optimize?max_num_segments=1?
四、segment 性能相關設置:
1、查看某個索引中所有 segment 的駐留內存情況:
curl -XGET 'http://host地址:port端口/_cat/segments/索引節點名稱?v&h=shard,segment,size,size.memory'
2、性能優化:
(1)合并策略:
合并線程是按照一定的運行策略來挑選 segment 進行歸并的。主要有以下幾條:
- ① index.merge.policy.floor_segment:默認 2MB,小于該值的 segment 會優先被歸并。
- ② index.merge.policy.max_merge_at_once:默認一次最多歸并 10 個 segment
- ③ index.merge.policy.max_merge_at_once_explicit:默認 forcemerge 時一次最多歸并 30 個 segment
- ④ index.merge.policy.max_merged_segment:默認 5 GB,大于該值的 segment,不用參與歸并,forcemerge 除外
(2)設置延遲提交:
根據上面的策略,我們也可以從另一個角度考慮如何減少 segment 歸并的消耗以及提高響應的辦法:加大 refresh 間隔,盡量讓每次新生成的 segment 本身大小就比較大。這種方式主要通過延遲提交實現,延遲提交意味著數據從提交到搜索可見有延遲,具體需要結合業務配置,默認值1s;
針對索引節點粒度的配置如下:
curl -XPUT http://host地址:port端口/索引節點名稱/_settings -d '{"index.refresh_interval":"10s"}'
(3)對特定字段field禁用 norms 和 doc_values 和 stored:
norms、doc_values 和 stored 字段的存儲機制類似,每個 field 有一個全量的存儲,對存儲浪費很大。如果一個 field 不需要考慮其相關度分數,那么可以禁用 norms,減少倒排索引內存占用量,字段粒度配置 omit_norms=true;如果不需要對 field 進行排序或者聚合,那么可以禁用 doc_values 字段;如果 field 只需要提供搜索,不需要返回則將 stored 設為 false;
總結
以上是生活随笔為你收集整理的Elasticsearch搜索引擎:ES的segment段合并原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ElasticSearch搜索引擎:常用
- 下一篇: Elasticsearch搜索引擎之缓存