爲什麼Elasticsearch查詢變得這麼慢了?

0、引言

Elasticsearch社區中經常看到慢查詢問題:“你能幫我看看Elasticsearch的響應時間嗎?”或者是:“我的ES查詢耗時很長,我該怎麼做?”

包含但不限於:Nested慢查詢、集羣查詢慢、range查詢慢等問題。
在這裏插入圖片描述

1、兩個維度

每當我們得到這些類型的問題時,我們首先要深入研究兩個主要方面:

  • 配置維度 - 查看當前系統資源和默認Elasticsearch選項。
  • 開發維度 - 查看查詢,其結構以及要搜索的數據的映射(Mapping)。

我們將首先關注開發方面的問題。 我們將獲得慢查詢,討論DSL查詢語言,並查看有助於改進Elasticsearch查詢的小型常規選項。

2、開發維度—你的查詢有多慢?

第一步是查看發送到羣集的查詢所花費的時間。 在研究如何打開慢速日誌時,Elasticsearch文檔可能有點不清楚,因此我將在下面展示一些示例。

  • 首先,Elasticsearch中有兩個版本的慢速日誌:索引慢速日誌(index slow logs )和搜索慢速日誌( search slow logs)。
    由於我們試圖解決的問題涉及慢查詢,我們將專注於搜索慢速日誌。 但是,如果在索引文檔/添加文檔時問題解決了性能問題,那麼我們將查看索引慢速日誌。

默認情況下,所有版本的Elasticsearch都會關閉慢速日誌,因此您必須對羣集設置和索引設置進行一些更新。
這些示例適用於使用elasticsearch 6.2,但您可以在此處找到所有以前的版本。
只需將$ES_version替換爲您正在使用的版本,
例如5.5版本設置官網參考:http://t.cn/E7Hq2NG。

向_cluster API發送放置請求以定義要打開的慢速日誌級別:警告,信息,調試和跟蹤。 (有關日誌記錄級別的更多信息參考:http://t.cn/E7Hqc5e。)

curl -XPUT http://localhost:$ES_PORT/_cluster/settings -H ‘Content-Type: application/json’ -d’
{
"transient" : {
"logger.index.search.slowlog" : "DEBUG",
"logger.index.indexing.slowlog" : "DEBUG"
}
}'
  • 所有慢速日誌記錄都在索引級別啓用,因此您可以再次向index _settings API發送請求以打開,但如果您每月,每季度等都在滾動更新索引,則還必須添加到索引模板中。

  • 將API調用調整爲索引設置以匹配您想要命中的慢日誌時間閾值。 (您可以設置爲0s以分析實例並收集正在發送的所有查詢,並設置爲-1以關閉慢速日誌。)
    使用您在_clustersettings中選擇使用的日誌級別設置。 在這個例子中,“DEBUG”。
    ES_PORT是一個持久的環境變量。

    curl -XPUT http://localhost:$ES_PORT/*/_settings?pretty -H 'Content-Type: application/json' -d 
    '{"index.search.slowlog.threshold.query.debug": "-1",
     "index.search.slowlog.threshold.fetch.debug": "-1",}'
    
  • 現在,您需要收集日誌。 每個分片生成慢速日誌並按數據節點收集。 如果您只有一個包含五個主分片的數據節點(這是默認值),您將在慢速日誌中看到一個查詢的五個條目。 由於Elasticsearch中的搜索發生在每個分片中,因此每個分片都會看到一個。 每個數據節點存儲慢速日誌,默認情況如下

location:/ var / log / elasticsearch / $ClusterID_index_slowlog_query
和 / var / log / elasticsearch / $ClusterID_index_slowlog_fetch。

如您所見,搜索慢速日誌再次根據搜索階段分解爲單獨的日誌文件:獲取(fetch)和查詢(query)。
現在我們在日誌中有結果,我們可以拉入一個條目並將其分開。

[2018-05-21T12:35:53,352][DEBUG ][index.search.slowlog.query] 
[DwOfjJF] [blogpost-slowlogs][4] took[1s], took_millis[0], types[], 
stats[], search_type[QUERY_THEN_FETCH], total_shards[5], 
source[{"query":{"match":{"name":{"query":"hello world",
 "operator":"OR","prefix_length":0,"max_expansions":50,
"fuzzy_transpositions" :true,"lenient":false,"zero_terms_query":
 "NONE","boost":1.0}}},"sort":[{"price": {"order":"desc"}}]}], 

在這裏,您看到:

 1. 日期
 2. 時間戳 
 3. 日誌級別 
 4. 慢速類型 
 5. 節點名稱 
 6. 索引名稱 
 7. 分片號 
 8. 時間花費 
 9. 查詢的主體(_source>)

一旦我們獲得了我們認爲花費的時間太長的查詢,我們就可以使用一些工具來分解查詢:

工具1:Profile API

Profile API提供有關搜索的信息頁面,並分解每個分片中發生的情況,直至每個搜索組件(match/range/match_phrase等)的各個時間。 搜索越詳細,_profile輸出越詳細。

工具2:The Kibana profiling 工具

這與_profileAPI密切相關。 它提供了各個搜索組件的完美的可視化效果表徵各個分解階段以及各階段查詢的時間消耗。 同樣,這允許您輕鬆選擇查詢的問題區域。
在這裏插入圖片描述

3、開發維度—Elasticsearch的查詢原理

現在我們已經確定了一個很慢的查詢,我們通過一個分析器profile來運行它。 但是,查看單個組件時間結果並未使搜索速度更快。 怎麼辦?

通過兩個階段(下面)瞭解查詢的工作原理,允許您以從速度和相關性方面獲得Elasticsearch最佳結果的方式重新設計查詢。
在這裏插入圖片描述

3.1 Query階段

  • 路由節點接受該查詢。
  • 路由節點識別正在搜索的索引(或多個索引)。
  • 路由節點生成一個節點列表,其中包含索引的分片(主要和副本的混合)。
  • 路由節點將查詢發送到節點(上一步節點列表列出的節點)。
  • 節點上的分片處理查詢。
  • 查詢(默認情況下)對前10個文檔進行評分。
  • 該列表將發送迴路由節點。

3.2 fetch階段

獲取階段由路由節點開始,路由節點確定每個分片發送的50個(5個分片×10個結果)結果中的前10個文檔。
路由節點向分片發出對前10個文檔的請求。 (可能是包含最高得分文檔的一個分片,或者它們可能分散在多個分片中。)

返回列表後,主節點會在查詢響應的_hits部分中顯示文檔。

4、開發維度—filter過濾器查詢優化

結果分數是Elasticsearch的關鍵。 通常,當您使用搜索引擎時,您需要最準確的結果。 例如,如果您正在搜索“蘋果”,您不希望結果包括“蘋果手機”。
Elasticsearch根據您提供的參數對查詢結果進行評分。
雖然查詢相關性不是本篇文章的重點,但重要的是在此提及,因爲如果您有快速搜索需求但結果不是您要查找的結果,則整個搜索都是浪費時間。
那麼,你如何加快搜索速度?

4.1 查詢時,使用query-bool-filter組合取代普通query

提高搜索性能的一種方法是使用過濾器。 過濾後的查詢可能是您最需要的。
首先過濾是很重要的,因爲搜索中的過濾器不會影響文檔分數的結果,因此您在資源方面使用很少的資源來將搜索結果範圍縮小到很小。
使用過濾查詢,結合使用布爾匹配,您可以在評分之前搜索包含X的所有文檔,或者不包含Y的所有文檔。此外,可以filter是可以被緩存的。
過濾器filter查詢不是加速Elasticsearch查詢的唯一方法。

【from騰訊】默認情況下,ES通過一定的算法計算返回的每條數據與查詢語句的相關度,並通過score字段來表徵。
但對於非全文索引的使用場景,用戶並不care查詢結果與查詢條件的相關度,只是想精確的查找目標數據。
此時,可以通過query-bool-filter組合來讓ES不計算score,
並且儘可能的緩存filter的結果集,供後續包含相同filter的查詢使用,提高查詢效率。

filter原理推薦閱讀:吃透 | Elasticsearch filter和query的不同

5、開發維度——其他優化

5.1 避免使用script查詢

避免使用腳本查詢來計算匹配。 推薦:建立索引時存儲計算字段。

例如,我們有一個包含大量用戶信息的索引,我們需要查詢編號以“1234”開頭的所有用戶。
您可能希望運行類似“source”的腳本查詢:“doc [‘num’]。value.startsWith(‘1234’)。”
此查詢非常耗費資源並且會降低整個系統的速度。 合理的建議:考慮在索引時添加名爲“num_prefix”的字段。
然後我們可以查詢“name_prefix”:“1234”。

5.2 避免使用wildcard查詢

主要原因:
wildcard類似mysql中的like,和分詞完全沒有了關係。

出現錯誤:
用戶輸入的字符串長度沒有做限制,導致首尾通配符中間可能是很長的一個字符串。 後果就是對應的wildcard Query執行非常慢,非常消耗CPU。

根本原因:
爲了加速通配符和正則表達式的匹配速度,Lucene4.0開始會將輸入的字符串模式構建成一個DFA (Deterministic Finite Automaton),帶有通配符的pattern構造出來的DFA可能會很複雜,開銷很大。

可能的優化方案:

  1. wildcard query應杜絕使用通配符打頭,實在不得已要這麼做,就一定需要限制用戶輸入的字符串長度。
  2. 最好換一種實現方式,通過在index time做文章,選用合適的分詞器,比如nGram tokenizer預處理數據,然後使用更廉價的term query來實現同等的模糊搜索功能。
  3. 對於部分輸入即提示的應用場景可以考慮優先使用completion suggester, phrase/term/suggeter一類性能更好,模糊程度略差的方式查詢,待suggester沒有匹配結果的時候,再fall back到更模糊但性能較差的wildcard, regex, fuzzy一類的查詢。
    詳盡原理參考:https://elasticsearch.cn/article/171

5.3 合理使用keyword類型

ES5.x裏對數值型字段做TermQuery可能會很慢。

在ES5.x+裏,一定要注意數值類型是否需要做範圍查詢,看似數值,但其實只用於Term或者Terms這類精確匹配的,應該定義爲keyword類型。

典型的例子就是索引web日誌時常見的HTTP Status code。

詳盡原理參考:https://elasticsearch.cn/article/446

5.4 控制字段的返回

一是:數據建模規劃的時候,在Mapping節點對於僅存儲、是否構建倒排索引通過enabled、index參數進行優化。
二是:_source控制返回,不必要的字段不需要返回,舉例:採集的原文章詳情內容頁,根據需要決定是否返回。

5.5 讓Elasticsearch幹它擅長的事情

在檢索/聚合結果後,業務系統還有沒有做其他複雜的操作,花費了多少時間?
這塊是最容易忽視的時間耗費擔當。

Elasticsearch顯然更擅長檢索、全文檢索,其他不擅長的事情,儘量不要ES處理。比如:頻繁更新、確保數據的ACID特性等操作。

6、配置維度——核心配置

6.1 節點職責明晰

區分路由節點、數據節點、候選主節點。

路由節點的主要優點是:

  1. 由於路由節點減少了搜索和聚合的壓力,因此數據節點上的內存壓力略有降低;
  2. “智能路由”——因爲他們知道所有數據存在的地方,他們可以避免額外的跳躍;“智能路由”——因爲他們知道所有數據存在的地方,他們可以避免額外的跳躍;
  3. 從架構上講,將路由節點用作集羣的訪問點非常有用,因此您的應用程序無需瞭解詳細信息。 從架構上講,將路由節點用作集羣的訪問點非常有用,因此您的應用程序無需瞭解詳細信息。

儘量將主節點與數據節點分開,因爲它將減少所有羣集的負載。

以下時間開始考慮專用主節點:

  1. 羣集大小開始變得難以駕馭,可能像10個節點或更高?
  2. 您會看到由於負載導致集羣不穩定(通常由內存壓力引起,導致長GC,導致主節點暫時從集羣中退出)您會看到由於負載導致集羣不穩定(通常由內存壓力引起,導致長GC,導致主節點暫時從集羣中退出)

分離主節點的主要目的是使“主節點的職責”與負載隔離,因爲高負載可能導致長GC,從而導致集羣不穩定。

分離主節點後,一個高負載的集羣只會影響數據節點(顯然仍然不好),但能保證主節點穩定,一旦集羣超載,基本上專門的主節點給你喘息的空間,而不是整個集羣走向崩潰。

另外,與數據節點相比,主節點通常可以非常“輕”。幾GB的RAM,中等CPU,普通磁盤等或許就能滿足需求(需要根據實際業務場景權衡)。

推薦閱讀:http://t.cn/E7HM4ML

6.2 分配合理的堆內存

搜索引擎旨在快速提供答案。 爲此,他們使用的大多數數據結構必須駐留在內存中。 在很大程度上,他們假設你爲他們提供了足夠的記憶。 如果不是這種情況,這可能會導致問題 - 不僅僅是性能問題,還有集羣的可靠性問題。

合理的堆內存大小配置建議:宿主機內存大小的一半和31GB,取最小值

推薦閱讀:https://blog.csdn.net/laoyang360/article/details/79998974

6.3 設置合理的分片數和副本數

shard數量設置過多或過低都會引發一些問題。

  1. shard數量過多,則批量寫入/查詢請求被分割爲過多的子寫入/子查詢,導致該index的寫入、查詢拒絕率上升;

對於數據量較大的index,當其shard數量過小時,無法充分利用節點資源,造成機器資源利用率不高 或 不均衡,影響寫入/查詢的效率。

對於每個index的shard數量,可以根據數據總量、寫入壓力、節點數量等綜合考量後設定,然後根據數據增長狀態定期檢測下shard數量是否合理。

騰訊基礎架構部數據庫團隊的推薦方案是:

  1. 對於數據量較小(100GB以下)的index,往往寫入壓力查詢壓力相對較低,一般設置3~5個shard,副本設置爲1即可(也就是一主一從,共兩副本)
  2. 對於數據量較大(100GB以上)的index:一般把單個shard的數據量控制在(20GB~50GB)

讓index壓力分攤至多個節點:可通過index.routing.allocation.totalshardsper_node參數,強制限定一個節點上該index的shard數量,讓shard儘量分配到不同節點上

綜合考慮整個index的shard數量,如果shard數量(不包括副本)超過50個,就很可能引發拒絕率上升的問題,此時可考慮把該index拆分爲多個獨立的index,分攤數據量,同時配合routing使用,降低每個查詢需要訪問的shard數量。

——建議參考:https://blog.csdn.net/laoyang360/article/details/78080602

6.4 設置合理的線程池和隊列大小

節點包含多個線程池,以便改進節點內線程內存消耗的管理方式。 其中許多池也有與之關聯的隊列,這允許保留掛起的請求而不是丟棄。

search線程——用於計數/搜索/推薦操作。 線程池類型爲fixed_auto_queue_size,大小爲int((available of available_ * 3)/ 2)+ 1,初始隊列大小爲1000。

5.X版本之後,線程池設置是節點級設置。因此,無法通過羣集設置API更新線程池設置。
查看線程池的方法:

GET /_cat/thread_pool

6.4 硬件資源的實時監控

排查一下慢查詢時間點的時候,注意觀察服務器的CPU, load average消耗情況,是否有資源消耗高峯,可以藉助:xpack、cerbero或者elastic-hd工具查看。

當您遇到麻煩並且羣集工作速度比平時慢並且使用大量CPU功率時,您知道需要做一些事情才能使其再次運行。 當Hot Threads API可以爲您提供查找問題根源的必要信息。 熱線程hot thread是一個Java線程,它使用高CPU量並執行更長的時間。

Hot Threads API返回有關ElasticSearch代碼的哪一部分是最耗費cpu或ElasticSearch由於某種原因而被卡住的信息。

熱線程使用方法:

GET /_nodes/hot_threads

7、小結

回答文章開頭的問題:——爲什麼Elasticsearch查詢變得這麼慢了?
和大數據量的業務場景有關,您可以通過幾個簡單的步驟優化查詢:

  1. 啓用慢速日誌記錄,以便識別長時間運行的查詢
  2. 通過_profiling API運行已識別的搜索,以查看各個子查詢組件的時間通過_profiling API運行已識別的搜索,以查看各個子查詢組件的時間
  3. 過濾,過濾,過濾過濾,過濾,過濾
    在這裏插入圖片描述

Elasticsearch優化非一朝一夕之功,需要反覆研究、實踐甚至閱讀源碼分析。
本文綜合了國外、國內很多優秀的實踐建議,核心點都已經實踐驗證可行。
歡迎大家留言討論!

參考:
http://t.cn/E7HJaPI
http://t.cn/RQSwH4X
http://t.cn/RInoI4c

這裏寫圖片描述
打造Elasticsearch基礎、進階、實戰第一公衆號!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章