本文首發於 vivo互聯網技術 微信公衆號 mp.weixin.qq.com/s/qwkZKLb_g…
英文原文:qbox.io/blog/elasti…
做者:Adam Vanderbush
譯者:楊振濤node
目錄json
Elasticsearch搜索調優權威指南,是QBOX在其博客上發佈的系列文章之一,本文是該系列的第一篇,主要從文檔建模、內存分配、文件系統緩存、GC和硬件等方面介紹了優化查詢性能的一些經驗。數組
Elasticsearch 5.0.0確實是在2.x以後的一個大版本,爲你們帶來了許多新東西。Elasticsearch如今做爲Elastic Stack中的一員,與整個技術棧的其餘產品的版本號已經對齊,如今Kibana、Logstash、Beats和Elasticsearch全都是5.0版本了。緩存
這個版本的Elasticsearch是目前爲止最快、最安全、最彈性,也是最易用的,並且還帶來了不少的改進和新特性。安全
咱們已經經過「Elasticsearch性能調優權威指南」系列,介紹了一些性能調優的基本經驗和方法,解釋了每一步最關鍵的系統設置和衡量指標。該系列共分下列3個部分:性能優化
索引決策也很重要,它對如何搜索數據有很大的影響。若是是一個字符串字段,是否須要分詞或歸一化?若是是,怎麼作?若是是一個數值型屬性,須要哪一種精度?還有不少其餘類型,好比date-time、geospatial shape以及父子關係等,須要更多特別的考慮。bash
咱們也經過一個系列教程討論了「Elasticsearch索引性能優化」,介紹了一些通用的技巧和方法,來最大化索引的吞吐量並下降監控和管理的負載。該教程分以下3個部分:微信
本文旨在推薦一些搜索調優技術、策略以及Elasticsearch 5.0及以上的推薦特性。網絡
內部對象屬性數組並不像指望的那樣工做。**Lucene **中沒有內部對象的概念,因此Elasticsearch把對象層次展開到一個由屬性名稱和屬性值組成的簡單列表中。如下列文檔爲例:app
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d '{ "group" : "fans", "user" : [ { "first" : "John", "last" : "Smith" }, { "first" : "Alice", "last" : "White" } ] }'
複製代碼
該請求會在內部轉換爲以下的文檔形式:
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}
複製代碼
若是須要索引對象數組,並維護數組中每一個對象的依賴關係,應當使用內嵌數據類型而不是對象數據類型。內嵌對象在內部會把數組中的每一個對象看成單獨的隱藏文檔來索引,即便用下述內嵌查詢,能夠單獨查詢每一個內嵌對象:
curl -XPUT 'ES_HOST:ES_PORT/my_index?pretty' -H 'Content-Type: application/json' -d '{ "mappings": { "my_type": { "properties": { "user": { "type": "nested" } } } } }'
curl -XPUT 'ES_HOST:ES_PORT/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d '{ "group" : "fans", "user" : [ { "first" : "John", "last" : "Smith" }, { "first" : "Alice", "last" : "White" } ] }'
curl -XGET 'ES_HOST:ES_PORT/my_index/_search?pretty' -H 'Content-Type: application/json' -d '{ "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "Smith" }} ] } } } } }'
curl -XGET 'ES_HOST:ES_PORT/my_index/_search?pretty' -H 'Content-Type: application/json' -d '{ "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "White" }} ] } }, "inner_hits": { "highlight": { "fields": { "user.first": {} } } } } } }'
複製代碼
當有一個主實體好比一篇博客文章,帶有一些有必定關係但又不是很是重要的其餘實體好比評論時,內嵌對象會很是有用。若是能根據評論內容來查詢到博客文章,那就很不錯,並且內嵌查詢和過濾器一塊兒提供了更快的join查詢能力。
內嵌對象模型的缺點以下:
爲了 增長 、修改 或 刪除 一個內嵌對象文檔,整個文檔必須重建索引;這就致使內嵌文檔越多開銷就越大。
搜索請求返回整個文檔,而不是隻返回匹配的內嵌文檔。雖然已經之後計劃支持返回根文檔的部分最配內嵌文檔,但目前仍然不支持。
有時候可能須要把主文檔和其關聯實體分離,這種分離由父子關係來提供。
經過創建另外一個文檔的父類型mapping,能夠在相同索引的文檔之間創建父子關係:
curl -XPUT 'ES_HOST:ES_PORT/my_index?pretty' -H 'Content-Type: application/json' -d '{ "mappings": { "my_parent": {}, "my_child": { "_parent": { "type": "my_parent" } } } }'
curl -XPUT 'ES_HOST:ES_PORT/my_index/my_parent/1?pretty' -H 'Content-Type: application/json' -d '{ "text": "This is a parent document" }'
curl -XPUT 'ES_HOST:ES_PORT/my_index/my_child/2?parent=1&pretty' -H 'Content-Type: application/json' -d '{ "text": "This is a child document" }'
curl -XPUT 'ES_HOST:ES_PORT/my_index/my_child/3?parent=1&refresh=true&pretty' -H 'Content-Type: application/json' -d '{ "text": "This is another child document" }'
curl -XGET 'ES_HOST:ES_PORT/my_index/my_parent/_search?pretty' -H 'Content-Type: application/json' -d '{ "query": { "has_child": { "type": "my_child", "query": { "match": { "text": "child document" } } } } }'
複製代碼
父子join對管理實體關係很是有用,尤爲是在索引時間比檢索時間很重要的情形下,可是它會帶來較大的開銷;父子查詢比同等的內嵌查詢要慢5到10倍。
父子關係使用了全局序列號來加速join操做。不管父子map是否使用了內存緩存或磁盤上的doc value,全局序列號仍然須要在索引起生任何改變時進行重建。
分片中的父代越多,全局序列號構建就越耗時。相對於須要父代和較少的子代, 父子關係最適合每一個父代有不少子代的情形。
全局序列號默認是 延遲 構建:refresh後的第一個父子查詢或聚合請求將會觸發構建全局序列號。這會讓用戶感知到一個明顯的潛在峯值。可使用eager_global_ordinals 來把查詢期構建全局序列號的成本轉移到refresh期,經過以下方式mapping _parent屬性:
curl -XPUT 'ES_HOST:ES_PORT/company -d ‘{ "mappings": { "branch": {}, "employee": { "_parent": { "type": "branch", "fielddata": { "loading": "eager_global_ordinals" } } } } }’ 複製代碼
這裏,_parent屬性的全局序列號將會在一個新的段搜索可見時被構建。
對於不少的父代,全局序列號要花費數秒鐘來構建。此時,須要增長refresh_interval,以便refresh的頻率更低,而全局序列號保持可用的時間更長。這將大幅減小每秒鐘重建全局序列號的CPU消耗。
對多代數據的Join(參考Grandparents and Grandchildren)能力聽起來很吸引人,但須要思考其代價:
對於運行中Elasticsearch,內存是須要密切監控的重要資源之一。Elasticsearch和Lucene經過JVM堆內存和文件系統緩存兩種方式來消耗內存。因爲Elasticsearch運行在Java虛擬機(JVM)中,因此JVM的GC週期和頻率也須要重點監控。
JVM堆內存
對於Elasticsearch一個「恰好合適」的JVM堆大小是很是重要的——不能設置過大或太小,緣由見後文。通常來講Elasticsearch的經驗值是分配少於50%的可用RAM給JVM堆,且不要超過32GB。
爲Elasticsearch分配過少的堆內存,那麼就會留給Lucene更多內存,而Lucene重度依賴於文件系統緩存來快速處理請求。無論怎樣也不能設置太小的堆內存,由於當應用因爲頻繁GC而面臨短時中斷時,可能會遭遇內存溢出錯誤或吞吐量降低。
Elasticsearch默認安裝時設置的JVM堆大小爲1GB,這在大多數狀況下都偏小。能夠經過環境變量來設置指望的對大小並重啓Elasticsearch:
export ES_HEAP_SIZE=10g
複製代碼
設置JVM堆大小的另外一種方式(至關於設置同樣的最小值和最大值,以防止從新調整堆大小),是在每次啓動Elasticsearch時經過命令行參數指定:
ES_HEAP_SIZE="10g" ./bin/elasticsearch
複製代碼
這兩種示例方式都是設置了10GB的堆大小,爲了驗證是否設置成功,執行:
curl -XGET http://ES_HOST:9200/_cat/nodes?h=heap.max
複製代碼
返回的輸出會顯示已正確地更新了最大堆內存。
垃圾回收
Elasticsearch依靠GC過程來釋放堆內存。因爲GC自己也要消耗資源(爲了釋放資源!),因此應當留意GC頻率和持續時間,以確認是否須要調整堆內存大小。設置過大的堆內存,換來的是更長的GC時間;這種過多的停頓很是危險,由於可能致使集羣誤認爲該節點網絡異常而失聯。
所以,Elasticsearch重度依賴文件系統緩存來加速搜索。通常須要保證至少有一半的可用內存用於文件系統緩存,這樣Elasticsearch才能保持索引數據的熱點區域都在物理內存中。
使用更快的硬件
若是搜索受限於I/O,應當考慮爲文件系統緩存分片更多內存(參考前文),或者購買更快的驅動。特別地,SSD公認地比機械磁盤性能好不少。儘量使用本地存儲,避免使用像 NFS 或 SMB 之類的遠程或網絡文件系統,也要注意像Amazon EBS這樣的虛擬化存儲。
Elasticsearch使用虛擬化存儲工做是沒有問題的,它由於快速和安裝簡單而受歡迎,但一樣不幸的是,在基礎上與專用的本地存儲相比它天生就比較慢。若是在EBS上建立了一個索引庫,請確認使用預分配的IOPS,不然很快就會被限流。
若是搜索受限於CPU,那麼應當考慮購買更快的CPU。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:labs2020 聯繫。