本文主要總結Elasticsearch性能優化方面的相關內容
1. 概述
性能優化是個涉及面非常廣的問題,不同的環境,不同的業務場景可能會存在不同的優化方案,本文只對一些相關的知識點做簡單的總結,具體方案可以根據場景自行嘗試。
1.1 性能測試
如果需要做性能調優,性能基準測試的工具必不可少,這裏可以選擇Rally
1.2 熱點線程
當集羣緩慢,使用大量的CPU資源時,可以使用熱點線程API來查看資源都執行在了哪些地方,並可以查看資源消耗的情況。
可以通過已下方式查看熱點線程
1. 全局的分析:/_nodes/hot_threads
2. 某個節點的分析:/_nodes/{nodesIds}/hot_threads
3. 常規配置
3.1 部署方案
- 當單機容納不下數據時,考慮多分片
- 當查詢性能不足時,考慮多副本。
- 對於一些高性能的服務器,可以考慮一臺服務器上部署多個實例
- 通過awarenss的相關配置,阻止分片及其副本部署在同一臺機器上。
- 儘量平均分配分片和副本。
- 當集羣較大時,考慮設計每個節點的角色。例如 節點只作爲查詢聚合節點,數據節點,或是主/候選主節點
3.2 避免內存交換
內存交換是指把內存頁寫入磁盤的過程,一般發生在物理內存不夠或是某些情況下操作系統認爲應該發生的時候發生。如果交換了的內存頁再次被需要,操作系統會從交換區加載到內存,該過程相對於內存操作而言是是比較慢的。
爲了保證ES的高效,在ES中應該避免內存交換的發生,如果達到此目的,需要進行如下配置:
1. 設置elasticserach.yml文件中的bootstrap.mlockall=true
2. 設置Xms與Xmx的值相同
3. 在/etc/security/limits.conf中添加elasticsearch -memlock unlimited
4. 在/etc/pam.d/common-session中添加session required pam_limits.so
5. 重啓es
3.3 採用G1垃圾回收機制代替默認CMS
3.4 索引刷新頻率(refresh_interval)
該參數基本遵循如下規律:
1. 刷新越快,數據更新越快,但是查詢與索引的效率越低
2. 雖然增加該值,可以一定程度的提升效率,但是超過一定數值後,提升的效果將微乎其微。
3.5 線程池調優
如果你發現ES實例的資源沒有100%飽和,但卻受到了拒絕執行的錯誤,此時可能就需要調整ES的線程池了。可以嘗試增加併發的線程數或是增加隊列的長度。在調整的過程中需要注意的是,當併發線程數到一個很大的數值時,會產生大量的CPU上下文切換,進而導致性能下降。大量的隊列也可能會出現隊列大量積壓的情況。
3.6 調整段合併過程
一般情況下,如果希望查詢的速度更快,就需要更少的段。例如設置index.merge.policy.merge_factory低於默認值10,會導致更少的段,更低的RAM消耗,更快的查詢執行速度但是會出現更慢的索引速度。如果設置的index.merge.policy.merge_factory較高則會出現相反的情況。
另外需要注意的是,默認情況下ES會限制合併的速度在20MB/s.如果使用的是固態硬盤或是I/O效率更高的設備,則可以適當的增加限制的速度。
4. 高查詢頻率下的優化
本章節主要針對高查詢吞吐量場景的優化方案進行總結
4.1 查詢緩存
分片查詢緩存的主要目的是緩存聚合,提示詞和命中數(不會緩存返回的文檔)
如果想要開啓mastering索引的查詢緩存,可以執行類似下面的操作
PUT /mastering/_settings
{ "index.requests.cache.enable": true }
查詢緩存默認使用節點堆棧的1%內存,可以通過下列方式對該值進行設置:
indices.requests.cache.size: 2%
4.2 使用doc_values優化查詢
緩存有時候可以帶來性能的顯著提高,但是對於某些場景緩存可能不是萬能的,例如:
1. 文檔頻繁更新
2. 查詢具有唯一性,且不可重複性,例如帶了時間或是id左右查詢條件
對於緩存需要容納全部數據的場景(例如,排序,聚合 等操作時),如果擁有大量的文檔,很容易碰到OOM的問題,此時可以考慮使用doc_values的特性。
在 Elasticsearch 中,Doc Values 就是一種列式存儲結構,默認情況下每個字段的 Doc Values 都是啓用的,Doc Values 是在索引時創建的,當字段索引時,Elasticsearch 爲了能夠快速檢索,會把字段的值加入倒排索引中,同時它也會存儲該字段的 Doc Values
。
Elasticsearch 中的 Doc Values 常被應用到以下場景:
- 對一個字段進行排序
- 對一個字段進行聚合
- 某些過濾,比如地理位置過濾
- 某些與字段相關的腳本計算
因爲文檔值被序列化到磁盤,我們可以依靠操作系統的幫助來快速訪問。當 working set 遠小於節點的可用內存,系統會自動將所有的文檔值保存在內存中,使得其讀寫十分高速; 當其遠大於可用內存,操作系統會自動把 Doc Values 加載到系統的頁緩存中,從而避免了 jvm 堆內存溢出異常。
因爲 Doc Values 默認啓用,你可以選擇對你數據集裏面的大多數字段進行聚合和排序操作。但是如果你知道你永遠也不會對某些字段進行聚合、排序或是使用腳本操作,可以禁用特定字段的 Doc Values 。這樣不僅節省磁盤空間,也許會提升索引的速度。 例子如下:
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"session_id": {
"type": "string",
"index": "not_analyzed",
"doc_values": false
}
}
}
}
}
4.3 儘量使用過濾器
由於過濾器執行的過程中不會涉及評分的操作以及過濾器緩存的緣故,所以查詢應該優先考慮使用過濾器(例如,對於靜態的,不分詞的字段儘量使用過濾器)。
儘量使用路由
有着相同路路由值的數據會被保存到相同的分片上,於是在查詢時就可以將請求發送到指定的分片上,可以避免合併結果的開銷。
因此應該儘量使用路由
4.4 字段數據緩存和斷路
字段數據緩存主要用於在字段上排序或計算聚合時使用。它將所有字段值加載到內存中,以提供基於快速的基於這些值的操作。
Elasticsearch的字段數據緩存默認是沒有大小限制的,尤其是當在很多字段上進行分組和排序的時候。如果這些字段的基礎很高,很容易出現OOM.
爲此可以採取以下措施:
1. 限制字段緩衝區的大小:indices.fielddata.cache.size
2. 使用斷路器( Field data circuit breaker):配置斷路器後可以在滿足某些條件下拋出異常而不是OOM
如果查詢大量使用了字段數據緩存(聚合和排序),且頻繁的存在內存的問題,可以考慮使用doc_values進行替換。
4.5 控制size和shard_size
- size:參數定義最後聚合結果會返回多少組數據給客戶端。
- shard_size:和size類似,其主要作用在分片上
上述兩個值的增加會讓聚合結果更加精準,同事也會消耗過多的資源。降低這兩個值會降低精準度,但是會減少資源損耗。
5. 寫數據
5.1 批量索引
如果有大量數據需要索引,可以使用批量索引取代逐個文檔的索引。
因爲處理批量索引的時候是在批量數據處理線程池中執行的,所以批次的量也不能太大,否則會ES也會消耗過多的內存來處理這些數據。
5.2 doc_values 與索引速度的取捨
ES爲了實現排序,聚合或是分組操作需要反轉字段,需要巨大的內存,doc_values可以解決這些問題。
但是在索引是doc_values也會產生一些額外的消耗,因此:
1. 如果不涉及排序,聚合或是分組操作,且對索引的吞吐量較大,可以考慮關閉doc_values
2. 如果海量數據的涉及排序,聚合或是分組操作,doc_values或許必不可少。
5.3 控制文檔的字段
文檔的大小在一定程度上會影響索引的速度,可以採取如下方式對大小進行控制
1. _all字段在很多場景下可能不會被使用,可以選擇性的對其進行關閉。
如果關閉了可以同時指定一個默認的查詢字段
curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d'
{
"mappings": {
"my_type": {
"_all": {
"enabled": false
},
"properties": {
"content": {
"type": "text"
}
}
}
},
"settings": {
"index.query.default_field": "content"
}
}
'
- 儘量減少文檔的大小和文本字段的數量。
5.4 考慮分片以及副本的數量
過多的副本會增加複製與傳輸的開銷
5.5 控制refresh與flush
對於需要進行大批量數據寫入,且在寫入過程中不需要查詢的場景,可以採取以下優化手段
- 修改translog的flush方式
默認情況下每次對索引的操作都會出發一次tranlog的flush操作,對於大批量數據寫入的場景,可以先改成定期flush,下列配置會根據index.translog.sync_interval的配置進行定期flush,等導完數據後,再恢復正常值
PUT test_index/_settings
{
"index":{
"translog.durability":"async"
}
}
- 修改refresh的時間
Searcher會更具refresh_interval的配置定期的更新索引,在大量數據導入的情況下,可以先將該值改爲-1,等導完數據後,再恢復正常值
PUT test_index/_settings
{
"index":{
"refresh_interval":"-1"
}
}
5.6 索引期間的內存緩存
增加節點的索引緩存大小(indices.memory.index_buffer_size,默認使用10%),也將有利於索引吞吐量的增加。
該配置主要是針對節點的,例如節點有20GB的內存,且有10十個分片,則每個分片大約分的200MB的內存作爲索引緩存(20GB*10%/10=200MB)