Profile API 性能分析
平時開發的過程中我們可能需要對一些查詢操作進行優化,而優化之前的工作就是要對操作的性能進行分析,而ES提供了Profile API來幫助用戶進行性能分析。它讓用戶瞭解如何在較低的級別執行搜索請求,這樣用戶就可以理解爲什麼某些請求比較慢,並採取措施改進它們。
需要注意的是Profile API不測量網絡延遲、搜索資源獲取階段、請求在隊列中花費的時間或在協調節點上合併碎片響應時花費的時間。
需要注意的是開啓性能分析會給查詢帶來非常大的性能開銷。所以不要嘗試將一個開啓了性能分析的請求和爲開啓性能分析的請求比對時間效率。
開啓性能分析
需要開啓性能分析,只需要在原有請求中設置"profile": true
。而當查詢嵌套的內容比較多的時候返回的性能分析內容會非常冗長,所以可以在請求URI後面設置?human=true
獲取比較清晰的結構。
性能分析結構
下面依舊是使用之前kibana創建的kibana_sample_data_ecommerce
做測試。下面是一個簡單的查詢請求。
GET /kibana_sample_data_ecommerce/_search?human=true
{
"profile": true,
"_source": false,
"query": {
"match": {
"category": "Women's Shoes"
}
}
}
返回內容
性能分析的相關報告在profile
對象內。
{
"took": 11,
"timed_out": false,
"_shards": {
......
},
"hits": {
......
},
"profile": {
"shards": [
{
"id": "[tMhvMfVrTnGguu5QJ-1j-Q][kibana_sample_data_ecommerce][0]",
"searches": [
{
"query": [
{
"type": "BooleanQuery",
"description": "category:women's category:shoes",
"time": "14.6ms",
"time_in_nanos": 14642543,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 3372,
"shallow_advance_count": 0,
"set_min_competitive_score": 0,
"next_doc": 12574965,
"match": 182802,
"next_doc_count": 3378,
"score_count": 3372,
"compute_max_score_count": 0,
"compute_max_score": 0,
"advance": 0,
"advance_count": 0,
"score": 558388,
"build_scorer_count": 12,
"create_weight": 161677,
"shallow_advance": 0,
"create_weight_count": 1,
"build_scorer": 1154576
},
"children": [
{
"type": "TermQuery",
"description": "category:women's",
"time": "1.3ms",
"time_in_nanos": 1326026,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 50,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 2466,
"compute_max_score_count": 50,
"compute_max_score": 248144,
"advance": 312952,
"advance_count": 2472,
"score": 201148,
"build_scorer_count": 18,
"create_weight": 82500,
"shallow_advance": 82773,
"create_weight_count": 1,
"build_scorer": 393452
}
},
{
"type": "TermQuery",
"description": "category:shoes",
"time": "1ms",
"time_in_nanos": 1089732,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 48,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 2080,
"compute_max_score_count": 48,
"compute_max_score": 183654,
"advance": 454098,
"advance_count": 2086,
"score": 164281,
"build_scorer_count": 18,
"create_weight": 36149,
"shallow_advance": 79552,
"create_weight_count": 1,
"build_scorer": 167717
}
}
]
}
],
"rewrite_time": 12723,
"collector": [
{
"name": "CancellableCollector",
"reason": "search_cancelled",
"time": "1.1ms",
"time_in_nanos": 1112587,
"children": [
{
"name": "SimpleTopScoreDocCollector",
"reason": "search_top_hits",
"time": "739.7micros",
"time_in_nanos": 739707
}
]
}
]
}
],
"aggregations": [
]
}
]
}
}
上面是一個非常簡單的查詢並不存在聚合內容,返回的分析報告相對也是比較簡單的。
參數解析
參數 | 說明 |
---|---|
id | 對於參與響應的每個碎片,將返回一個分析報告,並由惟一的ID標識 |
query | 顯示查詢的執行的詳細分析內容 |
rewrite_time | 表示累計重寫時間的時間 |
collector | 關於運行搜索的Lucene收集器的分析 |
aggregations | 聚合分析的詳細信息,上面內容因爲不存在聚合分析所以爲空 |
shards
因爲針對一個索引的搜索可能涉及一個或者多個碎片。所以分析報告中shards
是一個對象數組,每個分片都列出其ID,用來標識不同的分片。ID的命名格式節點ID+索引名稱+分片索引
query
其是對基礎Lucene索引執行的查詢。用戶提交的大多數搜索請求都只針對Lucene索引執行一次搜索。但是偶爾會執行多個搜索,比如包含一個全局聚合(需要爲全局上下文執行第二個“match_all”查詢)。
查詢分析
query
部分包含Lucene在特定碎片上執行查詢樹的詳細時間。這個查詢樹的結構會類似於最初請求數據時候的查詢結構。其會在description
字段中提供類似的解釋。下面看下上面例子的分析報告
"query" : [
{
"type" : "BooleanQuery",
"description" : "category:women's category:shoes",
"time" : "3.5ms",
"time_in_nanos" : 3540328,
"breakdown" : {
......
},
"children" : [
{
"type": "TermQuery",
"description": "category:women's",
"time": "1.3ms",
"time_in_nanos": 1326026,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 50,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 2466,
"compute_max_score_count": 50,
"compute_max_score": 248144,
"advance": 312952,
"advance_count": 2472,
"score": 201148,
"build_scorer_count": 18,
"create_weight": 82500,
"shallow_advance": 82773,
"create_weight_count": 1,
"build_scorer": 393452
}
},
{
"type": "TermQuery",
"description": "category:shoes",
"time": "1ms",
"time_in_nanos": 1089732,
"breakdown": {
......
}
}
]
}
],
上面的查詢條件被Lucene重寫爲一個帶有兩個子句,type
字段顯示Lucene類名,description
字段顯示了查詢的Lucene解釋文本,用於幫助區分查詢的各個部分。time_in_nanos
字段顯示整個查詢的花費(注意計時是以納秒爲單位列出的)。然後子數組列出可能存在的所有子查詢。
breakdown
細分組件列出了底層Lucene執行的詳細時間統計
"breakdown" : {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 50,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 2466,
"compute_max_score_count": 50,
"compute_max_score": 248144,
"advance": 312952,
"advance_count": 2472,
"score": 201148,
"build_scorer_count": 18,
"create_weight": 82500,
"shallow_advance": 82773,
"create_weight_count": 1,
"build_scorer": 393452
},
breakdown參數的意義
參數名稱 | 說明 |
---|---|
create_weight | Lucene中的查詢必須能夠跨多個IndexSearchers重用,而很多查詢需要統計和它對應的索引相關的信息,所以Lucene要求每個查詢生成一個Weight對象,它作爲一個臨時上下文對象來保存與相關聯的狀態。此參數表示創建這個對象花費的時間 |
build_scorer | 構建一個計分器所花費的時間 |
next_doc | Lucene中next_doc方法,統計的是確定下一個匹配文檔所需的時間 |
advance | Lucene中advance方法,next_doc的低級版本,用來查找下一個匹配的文檔。因爲並不是所有查詢使用next_doc,所以advance也統計了此類查詢的時間 |
match | 有些查詢比如短語查詢(phrase queries)需要對文檔進“近似地”匹配,此時查詢過程中會存在第二階段檢查。其作用是匹配統計量的度量 |
score | 統計了爲文檔打分的時間 |
*_count | 記錄特定方法的調用次數。例如,“next_doc_count”:2,表示在兩個不同的文檔上調用了nextDoc()方法。這可以通過比較不同查詢組件之間的計數來幫助判斷查詢的選擇性。 |
collector
Lucene爲查詢定義了一個收集器(Collector),負責協調遍歷、評分以及匹配文檔的收集。
"collector": [
{
"name": "CancellableCollector",
"reason": "search_cancelled",
"time": "1.1ms",
"time_in_nanos": 1112587,
"children": [
{
"name": "SimpleTopScoreDocCollector",
"reason": "search_top_hits",
"time": "739.7micros",
"time_in_nanos": 739707
}
]
}
]
collector收集器
reason
字段嘗試給出收集器類名的簡單英文描述。而目前官方網站列出的收集器包含下面內容
收集器 | 收集器作用 |
---|---|
search_sorted | 對文件進行評分和分類的收集者。這是最常見的收集器,在大多數簡單的搜索中都可以看到 |
search_count | 僅計算與查詢匹配的文檔數量,但不獲取源的收集器。這可以在指定size: 0時看到 |
search_terminate_after_count | 在找到n個匹配文檔後終止搜索執行的收集器。這可以在指定terminate_after_count查詢參數時看到 |
search_min_score | 只返回得分大於n的匹配文檔的收集器。當指定了頂級參數min_score時,可以看到這種情況 |
search_multi | 一個封裝多個收集器的收集器。當搜索、聚合、全局agg和post_filters在單個搜索中組合時,可以看到這一點 |
search_timeout | 在一段指定時間後停止執行的收集器。這可以在指定超時頂級參數時看到 |
aggregation | Elasticsearch用於對查詢範圍運行聚合的收集器。單個聚合收集器用於收集所有聚合的文檔 |
global_aggregation | 針對全局查詢範圍而不是指定查詢執行聚合的收集器。 |
search_top_hits以及search_cancelled
關於這兩個官方案例使用的收集器,在其文檔中並沒有說明,而我查詢了stackoverflow
也沒有發現相關內容。後來我在lucene
文檔中查到了org.apache.lucene.search.TopDocsCollector
類似的收集器類,主要是返回指定top數的文檔。
rewrite_time(重寫時間)
Lucene中的所有查詢都要經歷一次或者多次“重寫”過程。這個過程將一直持續下去,直到查詢停止變化。這個過程允許Lucene執行優化,例如刪除冗餘子句,替換一個查詢以獲得更有效的執行路徑等等。這個值是包含所有被重寫查詢的總時間。
aggregations Section 聚合性能分析
使用聚合的查詢時,其聚合內容的分析存在於aggregations
對象中,其嵌套結構類似於查詢請求時的嵌套結構。使用聚合的性能分析可以查看查詢中聚合內容消耗資源的詳情。
現在再次發出請求,此請求除了包含query
還覆蓋了aggs
聚合以及嵌套聚合。
GET /kibana_sample_data_ecommerce/_search
{
"profile": true,
"query": {
"term": {
"currency": {
"value": "EUR"
}
}
},
"aggs": {
"week": {
"terms": {
"field": "day_of_week"
}
},
"gender_agg": {
"global": {},
"aggs": {
"gender": {
"terms": {
"field": "customer_gender"
}
}
}
}
},
"post_filter": {
"match": {
"message": "some"
}
}
}
最後返回的性能信息的聚合部分如下面的內容
{
"profile": {
"shards": [
{
"id": "[tMhvMfVrTnGguu5QJ-1j-Q][kibana_sample_data_ecommerce][0]",
"aggregations": [
{
"type": "LowCardinality",
"description": "week",
"time_in_nanos": 1121184,
"breakdown": {
"reduce": 0,
"build_aggregation": 129073,
"build_aggregation_count": 1,
"initialize": 3620,
"initialize_count": 1,
"reduce_count": 0,
"collect": 983814,
"collect_count": 4675
}
},
{
"type": "GlobalAggregator",
"description": "gender_agg",
"time_in_nanos": 8733554,
"breakdown": {
"reduce": 0,
"build_aggregation": 34655,
"build_aggregation_count": 1,
"initialize": 12318,
"initialize_count": 1,
"reduce_count": 0,
"collect": 8681904,
"collect_count": 4675
},
"children": [
{
"type": "GlobalOrdinalsStringTermsAggregator",
"description": "gender",
"time_in_nanos": 6729126,
"breakdown": {
"reduce": 0,
"build_aggregation": 29463,
"build_aggregation_count": 1,
"initialize": 3148,
"initialize_count": 1,
"reduce_count": 0,
"collect": 6691838,
"collect_count": 4675
}
}
]
}
]
}
]
}
}
可以看到上面week
聚合部分使用了LowCardinality
,在同一層級上gender_agg
使用的是GlobalAggregator。在time_in_nanos
中詳細給出了每個聚合所消耗的時間。
時間分析(Timing Breakdown)
Timing Breakdown
對象內的數據分析了底層Lucene
執行的詳細時間消耗。以week
的時間分析爲例子。
"breakdown": {
"reduce": 0,
"build_aggregation": 129073,
"build_aggregation_count": 1,
"initialize": 3620,
"initialize_count": 1,
"reduce_count": 0,
"collect": 983814,
"collect_count": 4675
}
統計的意義如下:
參數描述
參數 | 說明 |
---|---|
initialize | 代表的是在開始文檔收集之前創建和初始化所消耗的時間 |
collect | 表示聚合在收集文檔階段話費的時間。 |
build_aggregation | 文檔收集完成後,創建準備好聚合的結果發送至reducing node的時間 |
reduce | 目前未使用的統計內容,稍後將添加reduce階段的計時。 |
*_count | 統計方法的調用次數,比如上面結果中collect_count返回的是collect()的調用次數 |
Profiling使用時需要注意的內容
使用時需要注意
- 使用性能分析API會給搜索執行引入了較大的開銷。所以除非需要調試,其不應該在生產環境中被啓用。
- 一些簡單方法如collect, advance,next_doc開銷可能更大,因爲這些方法是在循環中調用的。
- 因爲分析帶來的額外消耗,所以不應該將啓用性能分析和未啓用性能分析的查詢進行比較。
Profiling存在的侷限
- 性能分析不能獲取搜索數據獲取以及網絡開銷
- 性能分析也不考慮在隊列中花費的時間、合併協調節點上的shard響應,或諸如構建全局序數之類的額外工作
- 性能分析目前無法應用於搜索建議(suggestions),高亮搜索(highlighting)
- 目前reduce階段無法使用性能分析統計結果
- 性能分析是個實驗性的功能
個人水平有限,上面的內容可能存在沒有描述清楚或者錯誤的地方,假如開發同學發現了,請及時告知,我會第一時間修改相關內容。假如我的這篇內容對你有任何幫助的話,麻煩給我點一個贊。你的點贊就是我前進的動力。