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