天天看點

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

0、引言

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

包含但不限于:Nested慢查詢、叢集查詢慢、range查詢慢等問題。

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

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最佳結果的方式重新設計查詢。

為什麼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 設定合理的分片數和副本數

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

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.5 硬體資源的實時監控

排查一下慢查詢時間點的時候,注意觀察伺服器的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查詢變得這麼慢了?

Elasticsearch優化非一朝一夕之功,需要反複研究、實踐甚至閱讀源碼分析。

本文綜合了國外、國内很多優秀的實踐建議,核心點都已經實踐驗證可行。

歡迎大家留言讨論!

參考:

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

打造Elasticsearch基礎、進階、實戰第一公衆号!

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

繼續閱讀