eBay的Elasticsearch性能調優實踐
Elasticsearch是一個基于Apache Lucene的開源搜索和分析引擎,允許用戶近實時地存儲、搜索和分析數據。Pronto是eBay托管Elasticsearch集群的平臺,使eBay內部客戶易于部署、運維和擴展Elasticsearch以進行全文搜索、實時分析和日志事件監控。今天Pronto管理著60多個Elasticsearch集群,達2000多個節點。日采集數據量達到180億個文檔,日均查詢量達到35億。該平臺提供了創建、修復、安全、監控、告警和診斷的一整套功能。
雖然Elasticsearch專為快速查詢而設計,但其性能在很大程度上取決于應用程序的場景、索引的數據量以及應用程序和用戶查詢數據的速度。本文總結了Pronto團隊面臨的挑戰以及應對挑戰所構建的流程和工具,還給出了對幾種配置進行基準測試的一些結果。
挑戰
迄今遇到的Pronto/Elasticsearch使用場景所面臨的挑戰包括:
- 高吞吐量:一些集群每天采集的數據高達5TB,一些集群每天的搜索請求超過4億。如果Elasticsearch無法及時處理這些請求,上游的請求將發生積壓。
- 低搜索延遲:對于性能比較關鍵的集群,尤其是面向線上的系統,低搜索延遲是必需的,否則用戶體驗將受到影響。
- 由于數據或查詢是可變的,所以最佳設置也是變化的。不存在所有情況都是最佳的設置。例如,將索引拆分成更多的分片對于費時的查詢是有好處的,但是這可能會影響其他查詢性能。
解決方案
為了幫助我們的客戶應對這些挑戰,Pronto團隊為用戶案例上線和整個集群生命周期,針對性能測試、調優和監控構建了一套策略方法。
- 預估集群大小:在新的用戶案例上線之前,收集客戶提供的信息,如吞吐量、文檔大小、文檔數量和搜索類型,以估計Elasticsearch集群的初始大小。
- 優化索引設計:與客戶一起評估索引設計。
- 索引性能調優:根據用戶場景進行索引性能和搜索性能調優。
- 搜索性能調優:使用用戶真實數據/查詢運行性能測試,比較和分析不同Elasticsearch配置參數的測試結果。
- 運行性能測試:在用戶案例上線后,集群將受到監控,并且每當數據發生變化,查詢更改或流量增加時,用戶都可以自由地重新運行性能測試。
預估集群大小
Pronto團隊為每種類型的機器和每個支持的Elasticsearch版本運行基準測試,收集性能數據,然后結合客戶提供的信息,估算群集初始大小,包括:
- 索引吞吐量
- 文檔大小
- 搜索吞吐量
- 查詢類型
- 熱點索引文檔數量
- 保留策略
- 響應時間需求
- SLA級別
優化索引設計
在開始索引數據和運行查詢之前,我們先考慮一下。索引到底表示什么?Elastic的官方答案是“具有某種相似特征的文檔集合”。因此,下一個問題是“應該使用哪些特征來對數據進行分組?應該把所有文檔放入一個索引還是多個索引?”,答案是,這取決于使用的查詢。以下是關于如何根據最常用的查詢組織索引的一些建議。
- 如果查詢有一個過濾字段并且它的值是可枚舉的,那么把數據分成多個索引。 例如,你有大量的全球產品信息被采集到Elasticsearch中,大多數查詢都有一個過濾條件“地區”,并且很少有機會運行跨地區查詢。如下查詢體可以被優化:
{
"query": {
"bool": {
"must": {
"match": {
"title": "${title}"
}
},
"filter": {
"term": {
"region": "US"
}
}
}
}
}
在這種情況下,如果索引按照美國、歐盟等地區分成幾個較小的索引,可以從查詢中刪除過濾子句,查詢性能會更好。如果我們需要運行一個跨地區查詢,我們可以將多個索引或通配符傳遞給Elasticsearch。
- 如果查詢有一個過濾字段并且它的值是不可枚舉的,建議使用路由。 通過使用過濾字段值作為路由鍵,我們可以將具有相同過濾字段值的文檔索引至同一個分片,并移除過濾子句。
例如,Elasticsearch集群中存有數以百萬記的訂單數據,大多數查詢都包含有買方ID作為限定從句。為每個買家創建索引是不可能的,所以我們不能通過買方ID將數據拆分成多個索引。一個合適的解決方案是,使用路由將具有相同買方ID的所有訂單放入相同分片中。然后幾乎所有的查詢都可以在匹配路由鍵的分片內完成。
- 如果查詢具有日期范圍過濾子句,則按日期建立數據。 這適用于大多數日志記錄和監控場景。我們可以按天、周或月組織索引,然后可以獲得指定的日期范圍內的索引列表,這樣,Elasticsearch只需要查詢一個較小的數據集而不是整個數據集。另外,當數據過期時,刪除舊的索引也很容易。
- 明確設置映射。 雖然Elasticsearch可以動態創建映射,但創建的映射可能并不適用于所有場景。例如,Elasticsearch 5.x中的默認字符串字段映射是“keyword”和“text”類型。這在很多情況下是沒有必要的。
- 如果文檔使用用戶定義的ID或路由進行索引,要避免造成分片不平衡。 Elasticsearch使用隨機ID生成器和散列算法來確保文檔均勻地分配給分片。當使用用戶定義的ID或路由時,ID或路由鍵可能不夠隨機,造成一些分片明顯比其他分片大。在這種情況下,這個分片上的讀/寫操作會比其他的慢得多。我們可以優化ID/路由鍵或使用 index.routing_partition_size (5.3和更高版本中可用)。
- 確保分片均勻分布在節點上。 一個節點如果比其他節點的分片多,則會比其他節點承擔更多的負載,成為整個系統的瓶頸。
索引性能調優
對于日志記錄和監控等重度索引場景,索引性能是關鍵指標。這里有一些建議:
- 使用批量請求。
- 使用多線程發送請求。
- 增加刷新時間間隔。 每次刷新事件發生時,Elasticsearch都會創建一個新的Lucene段,并在稍后進行合并。增加刷新時間間隔將降低創建和合并的開銷。請注意,文檔只有在刷新后才能搜索到。
性能和刷新時間間隔之間的關系
從上圖可以看出,隨著刷新時間間隔的增加,吞吐量增加,響應時間減少。我們可以使用下面的請求來檢查我們有多少段以及刷新和合并花了多少時間。
Index/_stats?filter_path= indices.**.refresh,indices.**.segments,indices.**.merges
- 減少副本數量。 對于每個索引請求,Elasticsearch需要將文檔寫入主分片和所有副本分片。顯然,副本過多會減慢索引速度,但另一方面,這將提高搜索性能。我們將在本文后面討論這個問題。
性能和副本數之間的關系
從上圖可以看出,隨著副本數增加,吞吐量下降,響應時間增加。
- 盡可能使用自動生成的ID。 Elasticsearch自動生成的ID保證是唯一的,能避免版本查找。如果客戶真的需要使用自定義的ID,我們建議選擇一個對Lucene友好的ID,比如零填充順序ID、UUID-1或者納秒級時間。這些ID具有一致的順序模式,能良好壓縮。相比之下,像UUID-4這樣的ID本質上是隨機的,壓縮率低,會降低Lucene的速度。
搜索性能調優
使用Elasticsearch的主要原因是支持搜索數據。用戶應該能夠快速找到他們正在尋找的信息。搜索性能取決于很多因素。
- 盡可能使用過濾器上下文(Filter)替代查詢上下文(Query)。 查詢子句用于回答“這個文檔與此子句相匹配的程度”,而過濾器子句用于回答“這個文檔是否匹配這個子句”,Elasticsearch只需要回答“是”或“否”,不需要為過濾器子句計算相關性分數,而且過濾器結果可以緩存。有關詳細信息,請參閱 查詢和過濾上下文 。
查詢和過濾器性能比較
- 增加刷新時間間隔。 正如我們在 索引性能調優 中所提到的,Elasticsearch每次刷新時都會創建一個新的段。增加刷新時間間隔將有助于減少段數并降低搜索的IO成本。并且,一旦發生刷新并且數據改變,緩存將會失效。增加刷新時間間隔可以使Elasticsearch更高效地利用緩存。
- 增加副本數。 Elasticsearch可以在主分片或副本分片上執行搜索。副本越多,搜索可用的節點就越多。
搜索性能和副本數之間的關系
從上圖可以看出,搜索吞吐量幾乎與副本數量成線性關系。注意在這個測試中,測試集群有足夠的數據節點來確保每個分片都有一個專有節點。如果這個條件不能滿足,搜索吞吐量就不會這么好。
- 嘗試不同的分片數。 “應該為索引設置多少分片呢?”這可能是最常見的問題。遺憾的是,沒有適合所有應用場景的分片數。這完全取決于你的情況。
分片太少會使搜索無法擴展。例如,如果分片數設置為1,則索引中的所有文檔都將存儲在一個分片中。對于每個搜索,只有一個節點能夠參與計算。如果索引中的文件數量很多,查詢會很耗時。從另一方面來說,創建的索引分片太多也會對性能造成不利影響,因為Elasticsearch需要在所有分片上運行查詢(除非在請求中指定了路由鍵),然后提取并合并所有返回的結果。
根據我們的經驗,如果索引小于1G,可以將分片數設置為1。對于大多數場景,我們可以將分片數保留為默認值5,但是如果分片大小超過30GB,我們應該增加分片 ,將索引分成更多的分片。創建索引后,分片數不能更改,但是我們可以創建新的索引并使用reindex API遷移數據。
我們測試了一個擁有1億個文檔,大約150GB的索引。我們使用了100個線程發送搜索請求。
搜索性能和分片數量之間的關系
從上圖可以看出,最優的分片數量為11個。開始時搜索吞吐量增大(響應時間減少),但隨著分片數量的增加,搜索吞吐量減小(響應時間增加)。
請注意,在這個測試中,就像在副本數測試中一樣,每個分片都有一個獨占節點。如果這個條件不能滿足,搜索吞吐量將不會像這個圖那樣好。
在這種情況下,我們建議你嘗試一個小于最優值的分片數,因為如果分片多,并且使每個分片都有一個獨占數據節點,那么就需要很多節點。
- 節點查詢緩存。 節點查詢緩存 只緩存過濾器上下文中使用的查詢。與查詢子句不同,過濾子句是“是”或“否”的問題。Elasticsearch使用位集(bit set)機制來緩存過濾結果,以便后面使用相同的過濾器的查詢進行加速。請注意,Elasticsearch只對保存超過10,000(或文檔總數的3%,以較大者為準)個文檔的段啟用查詢緩存。有關更多詳細信息,請參閱 緩存 。
我們可以使用下面的請求來檢查一個節點查詢緩存是否生效。
GET index_name/_stats?filter_path=indices.**.query_cache
{
"indices": {
"index_name": {
"primaries": {
"query_cache": {
"memory_size_in_bytes": 46004616,
"total_count": 1588886,
"hit_count": 515001,
"miss_count": 1073885,
"cache_size": 630,
"cache_count": 630,
"evictions": 0
}
},
"total": {
"query_cache": {
"memory_size_in_bytes": 46004616,
"total_count": 1588886,
"hit_count": 515001,
"miss_count": 1073885,
"cache_size": 630,
"cache_count": 630,
"evictions": 0
}
}
}
}
}
- 分片查詢緩存。 如果大多數查詢是聚合查詢,我們應該考慮 分片查詢緩存 。分片查詢緩存可以緩存聚合結果,以便Elasticsearch以低開銷直接處理請求。有幾件事情需要注意:
- 設置“size”為0。分片查詢緩存只緩存聚合結果和建議。它不會緩存命中,因此如果將size設置為非零,則無法從緩存中獲益。
- 查詢請求的負載(Payload)必須完全相同。分片查詢緩存使用請求負載作為緩存鍵,因此需要確保后續查詢請求的負載必須和之前的完全一致。由于負載中JSON鍵的順序變化會導致負載變化,故建議對負載的鍵進行排序來確保順序一致。
- 處理好日期時間。不要直接在查詢中使用像Date.now這樣的變量。否則,每個請求的請求體都不同,從而導致緩存始終無效。我們建議將日期時間整理為小時或天,以便更有效地利用緩存。
我們可以使用下面的請求來檢查分片查詢緩存是否有效。
GET index_name/_stats?filter_path=indices.**.request_cache
{
"indices": {
"index_name": {
"primaries": {
"request_cache": {
"memory_size_in_bytes": 0,
"evictions": 0,
"hit_count": 541,
"miss_count": 514098
}
},
"total": {
"request_cache": {
"memory_size_in_bytes": 0,
"evictions": 0,
"hit_count": 982,
"miss_count": 947321
}
}
}
}
}
- 僅檢索必要的字段。 如果文檔很大,而你只需要幾個字段,請使用 stored_fields 檢索需要的字段而不是所有字段。
- 避免搜索停用詞。 諸如“a”和“the”等停用詞可能導致查詢命中結果數暴增。假設你有一百萬個文檔。搜索“fox”可能會返回幾十個命中文檔,但搜索“the fox”可能會返回索引中的所有文檔,因為“the”幾乎出現在所有文檔中。Elasticsearch需要對所有命中的結果進行評分和排序,以致像“the fox”這樣的查詢會減慢整個系統。你可以使用停用詞過濾器來刪除停用詞,或使用“and”運算符將查詢更改為“the AND fox”,獲得更精確的結果。
如果某些單詞在索引中經常使用,但不在默認停用詞列表中,則可以使用 截止頻率 來動態處理它們。
- 如果不關心文檔返回的順序,則按_doc排序。 Elasticsearch默認使用“_score”字段按評分排序。如果不關心順序,可以使用"sort":"_doc"讓Elasticsearch按索引順序返回命中文檔,可以節省排序開銷。
- 避免使用腳本查詢(script query)計算動態字段,建議在索引時計算并在文檔中添加該字段。 例如,我們有一個包含大量用戶信息的索引,我們需要查詢以"1234"開頭的所有用戶。你可能運行一個腳本查詢,如"source":"doc['num'].value.startsWith('1234')"。這個查詢非常耗費資源,并且減慢整個系統。索引時考慮添加一個名為“num_prefix”的字段。然后我們可以查詢"name_prefix":"1234"。
- 避免使用通配符查詢。
運行性能測試
對于每一次變更,都需要運行性能測試來驗證變更是否適用。因為Elasticsearch是一個RESTful服務,所以可以使用Rally、Apache Jmeter和Gatling等工具來運行性能測試。因為Pronto團隊需要在每種類型的機器和Elasticsearch版本上運行大量的基準測試,而且需要在許多Elasticsearch集群上針對不同Elasticsearch配置參數運行性能測試,所以這些工具不能滿足我們的要求。
Pronto團隊建立了基于 Gatling 的在線性能分析服務,幫助客戶和我們運行性能測試和回歸測試。該服務提供的功能有:
- 輕松添加和編輯測試。用戶無需Gatling或Scala知識即可根據輸入的查詢或文檔結構生成測試。
- 順序運行多個測試,無需人工干預。該服務可以檢查狀態并在每次測試之前/之后更改Elasticsearch設置。
- 幫助用戶比較和分析測試結果。測試期間的測試結果和集群統計信息將保留下來,并可以通過預定義的Kibana可視化進行分析。
- 從命令行或Web UI運行測試。該服務提供了與其他系統集成的Rest API。
架構如下:
性能測試服務架構
用戶可以查看每個測試的Gatling報告,并查看Kibana預定義的可視化圖像,以便進一步分析和比較,如下所示。
Gatling報告
Gatling報告
總結
本文總結了在設計滿足高期望的采集和搜索性能的Elasticsearch集群時應該考慮的索引/分片/副本設計以及一些其他配置,還說明了Pronto如何在策略上幫助客戶進行初始規模調整、索引設計和調優以及性能測試。截至今天,Pronto團隊已經幫助包括訂單管理系統(OMS)和搜索引擎優化(SEO)在內的眾多客戶實現了苛刻的性能目標,從而為eBay的關鍵業務作出了貢獻。
Elasticsearch的性能取決于很多因素,包括文檔結構、文檔大小、索引設置/映射、請求率、數據集大小和查詢命中次數等等。針對一種情況的建議不一定適用于另一種情況,因此,徹底進行性能測試、收集數據、根據負載調整配置以及優化集群以滿足性能要求非常重要。
查看英文原文: https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/
感謝薛命燈對本文的審校。
來自:http://www.infoq.com/cn/articles/elasticsearch-performance-tuning-practice-at-ebay