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


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

1. 前言

Elasticsearch社區中經常看到慢查詢問題:“你能幫我看看Elasticsearch的響應時間嗎?”或者是:“我的ES查詢耗時很長,我該怎么做?”包含但不限於:Nested慢查詢、集群查詢慢、range查詢慢等問題。

2. 兩個維度

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

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

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

3. 開發維度

3.1 你的查詢有多慢?

Elasticsearch中有兩個版本的慢速日志:索引慢速日志(index slow logs )和搜索慢速日志( search slow logs)。 默認情況下,所有版本的Elasticsearch都會關閉慢速日志,因此您必須對群集設置和索引設置進行一些更新。有關日志記錄級別的更多信息參考:http://t.cn/E7Hqc5e

搜索慢速日志根據搜索階段分解為單獨的日志文件:獲取(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等)的各個時間。

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

3.2 Elasticsearch的查詢原理

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

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

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

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

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

3.3 filter過濾器查詢優化

Elasticsearch根據您提供的參數對查詢結果進行評分。如果您有快速搜索需求但結果不是您要查找的結果,則整個搜索都是浪費時間。那么,你如何加快搜索速度?

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

提高搜索性能的一種方法是使用過濾器:

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

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

3.4 其他優化

3.4.1 避免使用script查詢

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

3.4.2 避免使用wildcard查詢

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

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

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

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

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

3.4.3 合理使用keyword類型

ES5.x里對數值型字段做TermQuery可能會很慢。在ES5.x+里,一定要注意數值類型是否需要做范圍查詢,看似數值,但其實只用於Term或者Terms這類精確匹配的,應該定義為keyword類型。
典型的例子就是索引web日志時常見的HTTP Status code

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

3.4.4 控制字段的返回
  • 數據建模規划的時候,在Mapping節點對於僅存儲、是否構建倒排索引通過enabled、index參數進行優化。
  • _source控制返回,不必要的字段不需要返回,舉例:采集的原文章詳情內容頁,根據需要決定是否返回。
3.4.5 讓Elasticsearch干它擅長的事情

在檢索/聚合結果后,業務系統還有沒有做其他復雜的操作,花費了多少時間?這塊是最容易忽視的時間耗費擔當。Elasticsearch顯然更擅長檢索、全文檢索,其他不擅長的事情,盡量不要ES處理。比如:頻繁更新、確保數據的ACID特性等操作。

4. 配置維度

4.1 節點職責明晰

區分:路由節點數據節點候選主節點

區分路由節點/數據節點/候選主節點的主要優點是:

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

盡量將主節點與數據節點分開,因為它將減少所有群集的負載。
以下時間開始考慮專用主節點:

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

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

分離主節點后,一個高負載的集群只會影響數據節點(顯然仍然不好),但能保證主節點穩定,一旦集群超載,基本上專門的主節點給你喘息的空間,而不是整個集群走向崩潰。另外,與數據節點相比,主節點通常可以非常“輕”。幾GB的RAM,中等CPU,普通磁盤等或許就能滿足需求(需要根據實際業務場景權衡)。

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

4.2 分配合理的堆內存

搜索引擎旨在快速提供答案。 為此,他們使用的大多數數據結構必須駐留在內存中。 在很大程度上,他們假設你為他們提供了足夠的記憶。 如果不是這種情況,這可能會導致問題 - 不僅僅是性能問題,還有集群的可靠性問題。
合理的堆內存大小配置建議: 宿主機內存大小的一半31GB取最小值

推薦閱讀:干貨 | 吃透Elasticsearch 堆內存

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

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數量。

建議參考:Elasticsearch究竟要設置多少分片數?

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

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

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

5.X版本之后,線程池設置是節點級設置。因此,無法通過群集設置API更新線程池設置。

查看線程池的方法: GET /_cat/thread_pool

4.5 硬件資源的實時監控

排查一下慢查詢時間點的時候,注意觀察服務器的CPUload average消耗情況,是否有資源消耗高峰,可以借助:xpackcerbero或者elastic-hd工具查看。

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

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

熱線程使用方法: GET /_nodes/hot_threads

5 小結

回答文章開頭的問題:——為什么Elasticsearch查詢變得這么慢了?

和大數據量的業務場景有關,您可以通過幾個簡單的步驟優化查詢:

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM