{
"profile": true,//開啟 profile 參數(shù)
"query" : {
"match" : { "message" : "GET /search" }
}
}

就會發(fā)現(xiàn)返回的 json 結(jié)果里包含一個(gè) profile 模塊。由于 json 返回比較長,這里就不貼了,我們簡要的來看看 profile 模塊主要的組成結(jié)構(gòu):

{
"profile": {
"shards": [
{
"id": "[q2aE02wS1R8qQFnYu6vDVQ][my-index-000001][0]", //這里返回了分片的 id,由 節(jié)點(diǎn)名[q2aE02wS1R8qQFnYu6vDVQ] + 索引名[my-index-000001] + 分片號 [0]
"node_id": "q2aE02wS1R8qQFnYu6vDVQ",
"shard_id": 0,
"index": "my-index-000001",
"cluster": "(local)", //是不是本地集群解析的
"searches": [
{
"query": [...], // query 階段的耗時(shí),其中包括各種 query 執(zhí)行類、執(zhí)行描述以及相關(guān)方法的執(zhí)行耗時(shí)
"rewrite_time": 51443, // 總共的 rewrite 耗時(shí)
"collector": [...] // collector 階段的耗時(shí),包括階段名稱、階段描述和執(zhí)行耗時(shí)
}
],
"aggregations": [...], //分析聚合階段的各個(gè)執(zhí)行類、執(zhí)行描述以及相關(guān)方法的耗時(shí)
"fetch": {...} //fetch 階段的執(zhí)行類、執(zhí)行描述以及相關(guān)方法的耗時(shí)
}
]
}
}

注意:

  1. 添加_search?human=true可以增加 profile 數(shù)據(jù)的可讀性,在主要階段的耗時(shí)數(shù)據(jù)后加上ms,micros單位。
  2. 在各階段 breakdown 的方法里,數(shù)據(jù)單位默認(rèn)都是納秒(time_in_nanos)

1.2 kibana 的 Search Profiler

行業(yè)內(nèi)有句話“一圖勝千言”,清晰明了的圖形化工具能大大提高分析的效率。

更何況 ES 默認(rèn)返回 json 數(shù)據(jù)常常成百上千行,很容易讓人讀不下去。

因此 ES 在 kibana 上做了一個(gè)圖形化分析模塊,在 Dev Tools -> Search Profiler。

你可以將需要被解析的索引名和查詢 DSL 貼入相應(yīng)的區(qū)域進(jìn)行直接執(zhí)行Profile解析。

除了 query 階段,也可以看到有 aggregation 聚合階段。

view detail 我們還可以看到各個(gè)執(zhí)行方法的耗時(shí)。

除了解析執(zhí)行語句,也可以直接解析 profile 結(jié)果,將 profile API 返回 json 數(shù)據(jù)直接貼入查詢 DSL (Elasticsearch 查詢語句)的區(qū)域執(zhí)行。

不管是 Profile API 還是 kibana 的 Search Profiler,提供的只是 ES 執(zhí)行查詢各階段的方法細(xì)節(jié)。

想要判斷出各個(gè)階段的執(zhí)行效率是否最佳,有沒有使用誤區(qū),我們還得從 ES 查詢主要階段說起。

2、Elatsicsearch 檢索核心原理

ES 是一個(gè)分布式系統(tǒng),一個(gè)索引數(shù)據(jù)查詢需要匯總每個(gè)索引分片的結(jié)果。

如下圖,search 請求發(fā)送給 ES 后,會由內(nèi)部的協(xié)調(diào)節(jié)點(diǎn)發(fā)送到數(shù)據(jù)節(jié)點(diǎn),由數(shù)據(jù)節(jié)點(diǎn)上的分片去執(zhí)行具體的查詢?nèi)蝿?wù)。

同時(shí),大多數(shù)的 ES 查詢請求是一個(gè)二階段的查詢

第一階段數(shù)據(jù)節(jié)點(diǎn)上的分片接收到協(xié)調(diào)節(jié)點(diǎn)傳來的查詢請求,分片執(zhí)行查詢?nèi)蝿?wù)后,會將查詢到匹配的 DocID 反饋給協(xié)調(diào)節(jié)點(diǎn),協(xié)調(diào)節(jié)點(diǎn)會對各個(gè)分片的結(jié)果進(jìn)行匯總。

第二階段協(xié)調(diào)節(jié)點(diǎn)根據(jù)匯總后的 DocID,到數(shù)據(jù)對應(yīng)的分片上去獲取完整文檔。

整個(gè)過程叫做 query and fetch,query 和 fetch 也是分片在這兩個(gè)階段主要執(zhí)行的任務(wù),也是 profile 主要分析的階段(低版本只分析 query 階段)。

進(jìn)行二階段查詢的主要原因是為了減少 fetch 階段的資源損耗,獲取完整的文檔信息涉及數(shù)據(jù)文件的讀取和解壓縮,需要相對多的 IO 和 cpu 資源。

為了保證數(shù)據(jù)的召回率和準(zhǔn)確性,query 階段需要在每個(gè)分片上獲取查詢完整結(jié)果,比如一個(gè)查詢需要 Top 10 的結(jié)果,每個(gè)分片都會找出它的 Top 10,返回給協(xié)調(diào)節(jié)點(diǎn)。如果有 10 個(gè)分片,那么就需要返回 100 個(gè)結(jié)果,再由協(xié)調(diào)節(jié)點(diǎn)排出所有分片的 Top 10。

如果由一階段完成,則需要 fetch 100 個(gè)文檔,二階段則只需要 fetch 10 個(gè)文檔。

完整的二階段查詢流程如下圖:

注:這里的流程圖來自于文末的參考文檔《Elasticsearch內(nèi)核解析 – 查詢篇》

3、Profile API 可解析的階段

現(xiàn)在我們來看看 Profile API 可以解析的階段。

3.1 query 查詢階段

Query 階段是一階段查詢的主體,我理解是主要在二階段查詢流程中lucene::searchrescore這2個(gè)環(huán)節(jié),這里 Profile API 將查詢涉及的 lucene 執(zhí)行代碼類構(gòu)成的一個(gè)查詢樹,這個(gè)查詢樹通過名稱和描述把查詢條件與執(zhí)行類進(jìn)行完整的勾兌,這樣可以精確定位到是哪個(gè)查詢條件造成了慢查詢的主要耗時(shí)。

查詢樹重寫的過程是將查詢語句拆分成一個(gè)個(gè)不可被拆解的基礎(chǔ)查詢(比如 term 查詢)的過程。

大部分時(shí)候查詢樹的結(jié)構(gòu)與查詢 DSL 的結(jié)構(gòu)相似,有些情況下會重寫的面目全非,比如:ngram 分詞器,它會根據(jù) ngram 分詞器將查詢內(nèi)容進(jìn)行細(xì)維度的拆解,最終形成一大堆 TermQuery。

下面一個(gè)例子是一個(gè) match 查詢的拆解,首先來看查詢條件。

GET /_search
{
"query": {
"match": {
"message": {
"query": "get search"
}
}
}
}

返回的結(jié)果。

"query": [
{
"type": "BooleanQuery",
"description": "message:get message:search",
"time_in_nanos": "11972972",
"breakdown": {...},
"children": [
{
"type": "TermQuery",
"description": "message:get",
"time_in_nanos": "3801935",
"breakdown": {...}
},
{
"type": "TermQuery",
"description": "message:search",
"time_in_nanos": "205654",
"breakdown": {...}
}
]
}
]

這里的主要過程:

  1. 將 match 查詢重寫成一個(gè) boolean 查詢,這個(gè) boolean 查詢描述是message:get message:search,展現(xiàn)了其總耗時(shí)(包括它的子查詢耗時(shí))和 breakdown。
  2. boolean 查詢下還有兩個(gè)子查詢,是對 message 字段進(jìn)行 get 和 search 這兩個(gè)文本的 TermQuery。
  3. 分別展示了 TermQuery 的耗時(shí)/描述/breakdown。

值得注意的是,Query 階段是 ES 查詢中最容易出現(xiàn)慢查詢的階段

3.2 collector 聚集階段

Collector 也由各類的子 Collector 組成,它們的主要目標(biāo)是在分片內(nèi)收集各個(gè) segment 的查詢結(jié)果,實(shí)現(xiàn)排序,對自定義結(jié)果集過濾和收集等,這也導(dǎo)致了在查詢流程中,Add Collectors在執(zhí)行 lucene 的 search 之前。

Collector 耗時(shí)獨(dú)立于 Query 耗時(shí)。

它們是獨(dú)立進(jìn)行計(jì)算和組合的。由于 Lucene 執(zhí)行的性質(zhì),不可能將來自 Collector 的耗時(shí)“合并”到 Query 部分。

"collector": [
{
"name": "QueryPhaseCollector",
"reason": "search_query_phase",
"time_in_nanos": 775274,
"children" : [
{
"name": "SimpleTopScoreDocCollector",
"reason": "search_top_hits",
"time_in_nanos": 775274
}
]
}
]

Collector 提供的信息相對較少只提供了名稱/原因/耗時(shí)。各類原因如下:

3.3 rewrite 重寫階段

Lucene中的所有查詢都經(jīng)歷了一個(gè)“重寫”過程。查詢(及其子查詢)可能被重寫一次或多次,直到只剩基礎(chǔ)查詢。

這個(gè)過程允許Lucene執(zhí)行優(yōu)化,例如刪除冗余子句,替換一個(gè)查詢以獲得更有效的執(zhí)行路徑等。

例如,Boolean→Boolean→TermQuery可以重寫為TermQuery,最終把 boolean 查詢?nèi)冀馕鐾辍uery 查詢樹的變化也正是這個(gè)重寫過程的體現(xiàn)。

重寫過程復(fù)雜且難以顯示,因?yàn)椴樵兛赡軙l(fā)生巨大變化。總重寫時(shí)間不是顯示中間結(jié)果,該值是累積的,包含重寫所有查詢的總時(shí)間。

3.4 aggregations 聚合階段

聚合部分的結(jié)構(gòu)與 Query 部分類似,也是將查詢樹的結(jié)構(gòu)展現(xiàn)出來。

有時(shí)候聚合可能會返回 debug 信息,這些信息描述了聚合的底層執(zhí)行的特性,但大部分情況下沒什么用。隨著版本、聚合和聚合執(zhí)行策略等條件的變化,這些信息差異很大。

3.5 fetch 獲取階段

這個(gè)就是二階段查詢中 fetch 階段的耗時(shí)分析。

其主要內(nèi)容就是加載 store_field/source/docvalue 三類存儲信息。使用者也可以根據(jù)這三類字段處理格式的使用耗時(shí),進(jìn)行實(shí)際方案的選擇。

其中,next_reader展示的是每個(gè) segment 的讀取計(jì)數(shù)和耗時(shí),load_stored_fields中加載存儲字段所花費(fèi)的時(shí)間。

Debug 包含的是一些非耗時(shí)信息,特別是會展示 stored_fields 列出的 fetch 必須加載的存儲字段;如果它是一個(gè)空列表,那么 fetch 將完全跳過加載存儲字段。

4、使用局限

當(dāng)然 Profile API 和 kibana 的 Search Profiler 也有相關(guān)的使用限制:

  1. Profile API 不會測試網(wǎng)絡(luò)的開銷。所有的耗時(shí)數(shù)據(jù)不包含節(jié)點(diǎn)間的網(wǎng)絡(luò)耗時(shí)。
  2. Profile API 不包含在任務(wù)對列擁堵耗時(shí)、內(nèi)存構(gòu)建耗時(shí)、協(xié)調(diào)節(jié)點(diǎn)合并的耗時(shí)。
  3. kibana-Search Profiler 并不完全展示 Profile API 的信息。比如沒有 fetch 階段;沒有底層方法的執(zhí)行次數(shù)。
  4. kibana-Search Profiler 統(tǒng)計(jì)的總耗時(shí)是各個(gè)分片耗時(shí)的 sum 值,而一個(gè)查詢的耗時(shí)取決于各分片耗時(shí)的 max 值。

5、小結(jié)

Elasticsearch 中,分片是一個(gè)最小級別的“工作單元”,它保存了索引中所有數(shù)據(jù)的一部分,同時(shí)每一個(gè)分片還是一個(gè)Lucene實(shí)例。

因此 Elasticsearch 分片執(zhí)行查詢過程基礎(chǔ)類都與 lucene 相關(guān),了解 lucene 的查詢類可以增加對查詢執(zhí)行細(xì)節(jié)的理解。

相比起 lucene 的學(xué)習(xí)成本,Profile API 則提供了一種更為直觀高效又低成本的慢查詢分析工具。

通過聚焦各個(gè)執(zhí)行階段的耗時(shí),我們直接可以定位到最大耗時(shí)的查詢條件,它有可能是整個(gè)查詢過于復(fù)雜,也有可能是某個(gè)字段類型不適合查詢的場景。

也就是說,解決慢查詢的方法并不一定要從 ES/lucene 本身去尋找,因?yàn)樵斐陕樵兊脑虿⒉恢辉从诔绦虮旧恚鼤歉鞣N各樣的原因,甚至是系統(tǒng)配置的錯(cuò)誤、網(wǎng)絡(luò)不穩(wěn)定等等。

而 Profile API 給我們提供了更直接的印證。

6、更多慢查詢優(yōu)化建議

除了使用 Profile API 定位耗時(shí)的查詢條件外,這里附帶一些慢查詢優(yōu)化的建議,供讀者參考:

6.1 合理利用緩存

ES 設(shè)計(jì)了多種緩存模式,有效利用緩存能保持查詢的低延遲,做好緩存的監(jiān)控。

6.2 設(shè)計(jì)好字段類型

特別是針對關(guān)鍵詞字段,number 和 keyword 類型在查詢和聚合方面有完全不同的查詢效率,資源足夠的情況下,設(shè)計(jì)雙字段。

有興趣的讀一下這篇,

https://elasticsearch.cn/article/446

6.3 區(qū)分精確匹配和全文檢索的使用場景。

精確匹配注重查詢效率但是需要設(shè)計(jì)好數(shù)據(jù)模型;

全文檢索可以面對自然語言信息的提煉,但是解析效果依賴分詞器,并占用資源較多。

6.4 注意 MultiTermQuery 的出現(xiàn)

MultiTermQuery 是 wildcard/正則/前綴等查詢常見的執(zhí)行子類,這些查詢?nèi)菀滓l(fā)大規(guī)模的資源消耗。

6.5 靈活使用 doc value

Fetch 階段的高耗時(shí),可以通過行存屬性 source 與列存屬性 doc value 的讀取測試比較來選取最優(yōu)方案。

7、附加 breakdown:lucene 執(zhí)行類的類屬方法

這里的介紹主要用于參考,lucene 實(shí)現(xiàn)類的方法是各種基礎(chǔ)算法的實(shí)現(xiàn),優(yōu)化或者規(guī)避這些方法造成的高耗時(shí)需要對 ES 源碼以及 lucene 數(shù)據(jù)文件有相當(dāng)?shù)睦斫夂椭R儲備。

這也導(dǎo)致了,即便能看到慢查詢具體的花費(fèi)時(shí)間在哪個(gè)方法上,你也沒辦法輕易的改變它(除非自己重新實(shí)現(xiàn)基礎(chǔ)的算法去維護(hù)私有版本或者貢獻(xiàn)給社區(qū) PR)。

每個(gè)階段各個(gè)主要方法的耗時(shí)體現(xiàn)在 breakdown 內(nèi),下面做了詳盡匯總,僅供參考:

7.1 query 階段

方法名含義涉及l(fā)ucene的類或者方法
create_weightLucene要求每個(gè)查詢生成一個(gè)Weight對象,這個(gè)對象作為一個(gè)臨時(shí)上下文對象來保存多個(gè)IndexSearcher與query相關(guān)的狀態(tài)。權(quán)重度量顯示了這個(gè)過程需要多長時(shí)間Query抽象類createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost):Constructs an appropriate Weight implementation for this query.
build_scorer此參數(shù)顯示為查詢構(gòu)建Scorer所需的時(shí)間。Scorer是一種迭代匹配文檔并為每個(gè)文檔生成分?jǐn)?shù)的機(jī)制(例如,“foo”與文檔的匹配程度如何?)注意,這記錄了生成Scorer對象所需的時(shí)間,而不是實(shí)際對文檔進(jìn)行評分。一些查詢的Scorer初始化速度更快或更慢,這取決于優(yōu)化、復(fù)雜性等。如果啟用和/或適用于查詢,這還可以顯示與緩存相關(guān)的時(shí)間Scorer抽象類:A Scorer exposes an iterator() over documents matching a query in increasing order of doc id.
next_docLucene 方法next_doc返回與查詢匹配的下一個(gè)文檔的 Doc ID。此統(tǒng)計(jì)數(shù)據(jù)顯示確定下一個(gè)匹配的文檔所需的時(shí)間,該過程根據(jù)查詢的性質(zhì)而有很大差異,不同的實(shí)現(xiàn)類以及數(shù)據(jù)結(jié)構(gòu)會有完全不同的效率。Next_doc 是 advance() 的一種特殊形式,對于 Lucene 中的許多查詢來說更為方便。它相當(dāng)于 advance(docId() + 1)DISI 抽象類 nextDoc():Advances to the next document in the set and returns the doc it is currently on, or NO_MORE_DOCS if there are no more docs in the set.
advanceadvance是 next_doc 的“低級”版本:它用于查找下一個(gè)匹配的文檔,但需要調(diào)用查詢執(zhí)行額外的任務(wù),例如識別和跳過跳過等。但是,并非所有查詢都可以使用 next_doc,因此advance這些查詢也會計(jì)時(shí)。DISI抽象類 advance(int target):Advances to the first beyond the current whose document number is greater than or equal to target, and returns the document number itself.
match某些查詢(例如短語查詢)使用“兩階段”過程匹配文檔。首先,文檔“近似”匹配,如果近似匹配,則使用更嚴(yán)格(且昂貴)的過程再次檢查。第二階段驗(yàn)證是統(tǒng)計(jì)數(shù)據(jù)所衡量的match。由于只有少數(shù)查詢使用這個(gè)兩階段過程,因此match統(tǒng)計(jì)數(shù)據(jù)通常為零PhraseMatcher類:Base class for exact and sloppy phrase matching
score這記錄了通過評分器對特定文檔進(jìn)行 Scorer 所花費(fèi)的時(shí)間Scorer 抽象類 score():Returns the score of the current document matching the query.
*_count記錄特定方法的調(diào)用次數(shù)。例如,”next_doc_count”: 2, 表示該nextDoc()方法在兩個(gè)不同的文檔上被調(diào)用。這可以通過比較不同查詢類之間的計(jì)數(shù)來幫助判斷查詢的選擇性。

7.2 aggregations 階段

方法名含義
build_aggregation創(chuàng)建聚合對象需要的時(shí)間
initialize初始化聚合所耗費(fèi)的時(shí)間
collect對聚合結(jié)果進(jìn)行 collect
build_leaf_collector運(yùn)行聚合的 getLeafCollector() 方法所花費(fèi)的時(shí)間
post_collectionpost 查詢 collect 耗時(shí)
reducereduce 屬性保留以供將來使用,并且總是返回0
*_count記錄特定方法的調(diào)用次數(shù)

8、參考資料

[1] kibana search profiler:

https://www.elastic.co/guide/en/kibana/current/xpack-profiler.html

[2] Profile API:

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-profile.html

[3] Elasticsearch內(nèi)核解析 – 查詢篇:

https://zhuanlan.zhihu.com/p/34674517

作者介紹

金多安,Elastic 認(rèn)證專家,Elastic 資深運(yùn)維工程師,死磕 Elasticsearch 知識星球嘉賓,星球Top活躍技術(shù)專家,搜索客社區(qū)日報(bào)責(zé)任編輯

銘毅天下審稿并做了部分微調(diào)。

本文章轉(zhuǎn)載微信公眾號@SearchKit

上一篇:

UCI機(jī)器學(xué)習(xí)數(shù)據(jù)庫的Python API介紹

下一篇:

Go項(xiàng)目實(shí)戰(zhàn)-API路由的分模塊管理
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場景實(shí)測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對比試用API 限時(shí)免費(fèi)