Profile API
Profile APIはデバッグツールであり、検索実行に大きなオーバーヘッドを追加します。
検索リクエスト内の個々のコンポーネントの実行に関する詳細なタイミング情報を提供します。
Description
Profile APIは、ユーザーが検索リクエストが低レベルでどのように実行されるかを理解できるようにし、特定のリクエストが遅い理由を理解し、それを改善するための手段を講じることができるようにします。Profile APIは、他のことの中で、ネットワーク遅延、リクエストがキューで過ごす時間、またはコーディネートノードでのシャード応答のマージにかかる時間を測定しないことに注意してください。
Profile APIからの出力は、特に多くのシャードにわたって実行される複雑なリクエストに対して非常に冗長です。出力を理解するために、レスポンスを整形して表示することをお勧めします。
Examples
任意の _search
リクエストは、トップレベルの profile
パラメータを追加することでプロファイルできます:
Python
resp = client.search(
index="my-index-000001",
profile=True,
query={
"match": {
"message": "GET /search"
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
body: {
profile: true,
query: {
match: {
message: 'GET /search'
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
profile: true,
query: {
match: {
message: "GET /search",
},
},
});
console.log(response);
Console
GET /my-index-000001/_search
{
"profile": true,
"query" : {
"match" : { "message" : "GET /search" }
}
}
トップレベルの profile パラメータを true に設定すると、検索のプロファイリングが有効になります。 |
Console-Result
{
"took": 25,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 0.17402273,
"hits": [...]
},
"profile": {
"shards": [
{
"id": "[q2aE02wS1R8qQFnYu6vDVQ][my-index-000001][0]",
"node_id": "q2aE02wS1R8qQFnYu6vDVQ",
"shard_id": 0,
"index": "my-index-000001",
"cluster": "(local)",
"searches": [
{
"query": [
{
"type": "BooleanQuery",
"description": "message:get message:search",
"time_in_nanos" : 11972972,
"breakdown" : {
"set_min_competitive_score_count": 0,
"match_count": 5,
"shallow_advance_count": 0,
"set_min_competitive_score": 0,
"next_doc": 39022,
"match": 4456,
"next_doc_count": 5,
"score_count": 5,
"compute_max_score_count": 0,
"compute_max_score": 0,
"advance": 84525,
"advance_count": 1,
"score": 37779,
"build_scorer_count": 2,
"create_weight": 4694895,
"shallow_advance": 0,
"create_weight_count": 1,
"build_scorer": 7112295,
"count_weight": 0,
"count_weight_count": 0
},
"children": [
{
"type": "TermQuery",
"description": "message:get",
"time_in_nanos": 3801935,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 3,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 5,
"compute_max_score_count": 3,
"compute_max_score": 32487,
"advance": 5749,
"advance_count": 6,
"score": 16219,
"build_scorer_count": 3,
"create_weight": 2382719,
"shallow_advance": 9754,
"create_weight_count": 1,
"build_scorer": 1355007,
"count_weight": 0,
"count_weight_count": 0
}
},
{
"type": "TermQuery",
"description": "message:search",
"time_in_nanos": 205654,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 3,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 5,
"compute_max_score_count": 3,
"compute_max_score": 6678,
"advance": 12733,
"advance_count": 6,
"score": 6627,
"build_scorer_count": 3,
"create_weight": 130951,
"shallow_advance": 2512,
"create_weight_count": 1,
"build_scorer": 46153,
"count_weight": 0,
"count_weight_count": 0
}
}
]
}
],
"rewrite_time": 451233,
"collector": [
{
"name": "QueryPhaseCollector",
"reason": "search_query_phase",
"time_in_nanos": 775274,
"children" : [
{
"name": "SimpleTopScoreDocCollector",
"reason": "search_top_hits",
"time_in_nanos": 775274
}
]
}
]
}
],
"aggregations": [],
"fetch": {
"type": "fetch",
"description": "",
"time_in_nanos": 660555,
"breakdown": {
"next_reader": 7292,
"next_reader_count": 1,
"load_stored_fields": 299325,
"load_stored_fields_count": 5,
"load_source": 3863,
"load_source_count": 5
},
"debug": {
"stored_fields": ["_id", "_routing", "_source"]
},
"children": [
{
"type" : "FetchFieldsPhase",
"description" : "",
"time_in_nanos" : 238762,
"breakdown" : {
"process_count" : 5,
"process" : 227914,
"next_reader" : 10848,
"next_reader_count" : 1
}
},
{
"type": "FetchSourcePhase",
"description": "",
"time_in_nanos": 20443,
"breakdown": {
"next_reader": 745,
"next_reader_count": 1,
"process": 19698,
"process_count": 5
},
"debug": {
"fast_path": 5
}
},
{
"type": "StoredFieldsPhase",
"description": "",
"time_in_nanos": 5310,
"breakdown": {
"next_reader": 745,
"next_reader_count": 1,
"process": 4445,
"process_count": 5
}
}
]
}
}
]
}
}
検索結果が返されますが、簡潔さのためにここでは省略されています。 |
単純なクエリでも、レスポンスは比較的複雑です。より複雑な例に進む前に、部分ごとに分解してみましょう。
Console-Result
{
"profile": {
"shards": [
{
"id": "[q2aE02wS1R8qQFnYu6vDVQ][my-index-000001][0]",
"node_id": "q2aE02wS1R8qQFnYu6vDVQ",
"shard_id": 0,
"index": "my-index-000001",
"cluster": "(local)",
"searches": [
{
"query": [...],
"rewrite_time": 51443,
"collector": [...]
}
],
"aggregations": [...],
"fetch": {...}
}
]
}
}
応答に参加した各シャードのプロファイルが返され、ユニークなIDで識別されます。 | |
クエリがローカルクラスターで実行された場合、クラスター名はコンポジットIDから省略され、ここでは「(local)」とマークされます。リモート_clusterで実行されるプロファイルでは、クロスクラスター検索を使用して、「id」値は[q2aE02wS1R8qQFnYu6vDVQ][remote1:my-index-000001][0] のようになり、「cluster」値はremote1 になります。 |
|
クエリのタイミングやその他のデバッグ情報。 | |
累積リライト時間。 | |
各コレクターの名前と呼び出しタイミング。 | |
集約タイミング、呼び出し回数、デバッグ情報。 | |
フェッチタイミングとデバッグ情報。 |
検索リクエストは、インデックス内の1つ以上のシャードに対して実行される可能性があり、検索は1つ以上のインデックスをカバーする可能性があるため、プロファイルレスポンスのトップレベル要素はshard
オブジェクトの配列です。各シャードオブジェクトは、そのシャードを一意に識別するid
をリストします。IDの形式は[nodeID][clusterName:indexName][shardID]
です。検索がローカルクラスターに対して実行される場合、clusterNameは追加されず、形式は[nodeID][indexName][shardID]
です。
プロファイル自体は、1つ以上の「検索」で構成される場合があります。「検索」とは、基盤となるLuceneインデックスに対して実行されるクエリです。ユーザーによって提出されたほとんどの検索リクエストは、Luceneインデックスに対して単一のsearch
を実行するだけです。しかし、時折、グローバル集約を含むように複数の検索が実行されることがあります(これは、グローバルコンテキストのために二次的な「match_all」クエリを実行する必要があります)。
各search
オブジェクト内には、プロファイル情報の2つの配列があります: query
配列とcollector
配列。search
オブジェクトの横には、集約のプロファイル情報を含むaggregations
オブジェクトがあります。将来的には、suggest
、highlight
などのセクションが追加される可能性があります。
また、クエリのリライトに費やされた合計時間を示すrewrite
メトリックもあります(ナノ秒単位)。
他の統計APIと同様に、Profile APIは人間が読みやすい出力をサポートしています。これは、クエリ文字列に?human=true
を追加することで有効にできます。この場合、出力には、丸められた人間が読みやすいタイミング情報を含む追加のtime
フィールドが含まれます(例: "time": "391,9ms"
、"time": "123.3micros"
)。
Profiling Queries
Profile APIによって提供される詳細は、Luceneのクラス名や概念を直接公開しているため、結果を完全に解釈するにはかなりの高度なLuceneの知識が必要です。このページは、Luceneがクエリを実行する方法についての短期講座を提供し、Profile APIを使用してクエリを正常に診断およびデバッグできるようにすることを試みていますが、これはあくまで概要です。完全な理解のためには、Luceneのドキュメントや、場合によってはコードを参照してください。
とはいえ、遅いクエリを修正するために完全な理解が必要なわけではありません。特定のクエリのコンポーネントが遅いことがわかれば十分であり、advance
フェーズがそのクエリの原因である理由を必ずしも理解する必要はありません。
query Section
[](#88b1a2cf17d7289db8c7e15d472efb4c)
#### Console-Result
``````console-result
"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": {...}
}
]
}
]
`
分解タイミングは簡潔さのために省略されています。 |
プロファイル構造に基づいて、match
クエリはLuceneによって2つのクローズを持つBooleanQueryにリライトされたことがわかります(両方ともTermQueryを保持しています)。type
フィールドはLuceneのクラス名を表示し、しばしばElasticsearchの同等の名前と一致します。description
フィールドはクエリのLucene説明テキストを表示し、クエリの部分を区別するのに役立ちます(例: message:get
とmessage:search
は両方ともTermQueryであり、そうでなければ同一に見えます)。
`````breakdown`````フィールドは、時間がどのように使われたかについての詳細な統計を提供します。これについては後で見ていきます。最後に、`````children`````配列は、存在する可能性のあるサブクエリをリストします。2つの値(「get search」)を検索したため、BooleanQueryには2つの子TermQueriesがあります。これらは同一の情報(タイプ、時間、内訳など)を持っています。子は独自の子を持つことが許可されています。
### Timing Breakdown
`````breakdown`````コンポーネントは、低レベルのLucene実行に関する詳細なタイミング統計をリストします:
[](#f025cead6c1af8c26dd75e971dd82e1a)
#### Console-Result
``````console-result
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 5,
"shallow_advance_count": 0,
"set_min_competitive_score": 0,
"next_doc": 39022,
"match": 4456,
"next_doc_count": 5,
"score_count": 5,
"compute_max_score_count": 0,
"compute_max_score": 0,
"advance": 84525,
"advance_count": 1,
"score": 37779,
"build_scorer_count": 2,
"create_weight": 4694895,
"shallow_advance": 0,
"create_weight_count": 1,
"build_scorer": 7112295,
"count_weight": 0,
"count_weight_count": 0
}
`
タイミングは壁時計のナノ秒でリストされ、全く正規化されていません。全体のtime_in_nanos
に関するすべての注意事項がここに適用されます。内訳の意図は、A) Luceneのどの機械が実際に時間を消費しているか、B) 様々なコンポーネント間の時間の違いの大きさを感じ取ることです。全体の時間と同様に、内訳はすべての子の時間を含みます。
統計の意味は次のとおりです:
All parameters:
create_weight |
Luceneのクエリは、複数のIndexSearchers間で再利用可能でなければなりません(特定のLuceneインデックスに対して検索を実行するエンジンと考えてください)。これにより、Luceneは厄介な状況に置かれます。多くのクエリは、使用されるインデックスに関連する一時的な状態/統計を蓄積する必要がありますが、Query契約では不変でなければならないと規定されています。 これを回避するために、Luceneは各クエリにWeightオブジェクトを生成するように要求します。これは、この特定の(IndexSearcher、Query)タプルに関連する状態を保持する一時的なコンテキストオブジェクトとして機能します。 weight メトリックは、このプロセスにかかる時間を示します |
build_scorer |
このパラメータは、クエリのScorerを構築するのにかかる時間を示します。Scorerは、一致するドキュメントを反復処理し、ドキュメントごとにスコアを生成するメカニズムです(例: 「foo」がドキュメントにどれだけ一致するか)。 注意: これはScorerオブジェクトを生成するのに必要な時間を記録し、実際にドキュメントのスコアを付ける時間ではありません。一部のクエリは、最適化、複雑さなどに応じてScorerの初期化が早かったり遅かったりします。これにより、キャッシングに関連するタイミングが表示される場合もあります。 |
next_doc |
Luceneメソッドnext_doc は、クエリに一致する次のドキュメントのDoc IDを返します。この統計は、次の一致がどのドキュメントであるかを決定するのにかかる時間を示します。このプロセスは、クエリの性質によって大きく異なります。Next_docは、advance()の便利な特殊形式であり、多くのLuceneクエリにとって便利です。これは、advance(docId() + 1)に相当します。 |
advance |
advance はnext_docの「低レベル」バージョンです: 次の一致するドキュメントを見つけるという同じ目的を果たしますが、呼び出しクエリがスキップを特定して移動するなどの追加のタスクを実行する必要があります。ただし、すべてのクエリがnext_docを使用できるわけではないため、 advance もそのクエリのためにタイミングが計測されます。結合(例: Booleanのmust クローズ)は、advance の典型的な消費者です。 |
match |
フレーズクエリなどの一部のクエリは、「二段階」プロセスを使用してドキュメントに一致します。最初に、ドキュメントが「おおよそ」一致し、もしおおよそ一致すれば、より厳密(かつ高コスト)なプロセスで二度目にチェックされます。二段階の検証は、match 統計が測定するものです。たとえば、フレーズクエリは、フレーズ内のすべての用語がドキュメントに存在することを確認することによって、最初にドキュメントをおおよそチェックします。すべての用語が存在する場合、次に、用語がフレーズを形成するために順序通りであることを確認するために二段階の検証を実行します。これは、用語の存在を確認するよりも相対的に高コストです。 この二段階プロセスは、ほんの一握りのクエリでのみ使用されるため、 match 統計はしばしばゼロになります。 |
score |
これは、特定のドキュメントをそのScorerを介してスコア付けするのにかかる時間を記録します。 |
*_count |
特定のメソッドの呼び出し回数を記録します。たとえば、"next_doc_count": 2, は、nextDoc() メソッドが2つの異なるドキュメントで呼び出されたことを意味します。これは、異なるクエリコンポーネント間のカウントを比較することによって、クエリがどれだけ選択的であるかを判断するのに役立ちます。 |
collectors Section
Collectors部分のレスポンスは、高レベルの実行詳細を示します。Luceneは、「Collector」を定義することによって機能し、一致するドキュメントのトラバース、スコアリング、および収集を調整します。Collectorsは、単一のクエリが集約結果を記録し、スコープのない「グローバル」クエリを実行し、クエリ後のフィルターを実行する方法でもあります。
Console-Result
"collector": [
{
"name": "QueryPhaseCollector",
"reason": "search_query_phase",
"time_in_nanos": 775274,
"children" : [
{
"name": "SimpleTopScoreDocCollector",
"reason": "search_top_hits",
"time_in_nanos": 775274
}
]
}
]
トップレベルのコレクターQueryPhaseCollector
があり、子SimpleTopScoreDocCollector
を保持しています。SimpleTopScoreDocCollector
は、Elasticsearchで使用されるデフォルトの「スコアリングとソート」Collector
です。reason
フィールドは、クラス名の平易な英語の説明を提供しようとします。time_in_nanos
は、クエリツリー内の時間と似ています: すべての子を含む壁時計の時間です。同様に、children
はすべてのサブコレクターをリストします。集約が要求されると、QueryPhaseCollector
は集約を実行している理由aggregation
を持つ追加の子コレクターを保持します。
Collectorの時間は、クエリの時間とは独立しています。これらは独立して計算、結合、正規化されます!Luceneの実行の性質上、Collectorの時間をクエリセクションに「マージ」することは不可能であるため、別々の部分に表示されます。
参考までに、さまざまなコレクターの理由は次のとおりです:
search_top_hits |
ドキュメントをスコアリングおよびソートするコレクター。これは最も一般的なコレクターであり、ほとんどの単純な検索で見られます。 |
search_count |
クエリに一致するドキュメントの数をカウントするだけのコレクターですが、ソースを取得しません。これは、size: 0 が指定されているときに見られます。 |
search_query_phase |
クエリフェーズの一部として、トップヒットの収集と集約を組み込むコレクターです。これは、n 一致するドキュメントが見つかった後に検索実行を終了することをサポートします(terminate_after が指定されているとき)、およびn よりもスコアが高い一致するドキュメントのみを返すことができます(min_score が提供されているとき)。さらに、提供されたpost_filter に基づいて一致するトップヒットをフィルタリングできます。 |
search_timeout |
指定された期間が経過した後に実行を停止するコレクターです。これは、timeout トップレベルパラメータが指定されているときに見られます。 |
aggregation |
Elasticsearchがクエリスコープに対して集約を実行するために使用するコレクターです。単一のaggregation コレクターがすべての集約のためにドキュメントを収集するために使用されるため、名前の中に集約のリストが表示されます。 |
| global_aggregation
| 指定されたクエリではなく、グローバルクエリスコープに対して集約を実行するコレクターです。グローバルスコープは実行されたクエリとは必然的に異なるため、データセット全体を収集するために独自のmatch_allクエリを実行する必要があります(これがクエリセクションに追加されるのが見られます)。
rewrite Section
Luceneのすべてのクエリは「リライト」プロセスを経ます。クエリ(およびそのサブクエリ)は1回以上リライトされる可能性があり、プロセスはクエリが変更を停止するまで続きます。このプロセスにより、Luceneは冗長なクローズを削除したり、より効率的な実行パスのために1つのクエリを置き換えたりするなどの最適化を実行できます。たとえば、Boolean → Boolean → TermQueryは、すべてのBooleanがこの場合不要であるため、TermQueryにリライトできます。
リライトプロセスは複雑で表示が難しいため、クエリは大きく変化する可能性があります。中間結果を表示するのではなく、合計リライト時間は単に値(ナノ秒単位)として表示されます。この値は累積的であり、リライトされるすべてのクエリの合計時間を含みます。
A more complex example
やや複雑なクエリと関連する結果を示すために、次のクエリをプロファイルできます:
Python
resp = client.search(
index="my-index-000001",
profile=True,
query={
"term": {
"user.id": {
"value": "elkbee"
}
}
},
aggs={
"my_scoped_agg": {
"terms": {
"field": "http.response.status_code"
}
},
"my_global_agg": {
"global": {},
"aggs": {
"my_level_agg": {
"terms": {
"field": "http.response.status_code"
}
}
}
}
},
post_filter={
"match": {
"message": "search"
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
body: {
profile: true,
query: {
term: {
'user.id' => {
value: 'elkbee'
}
}
},
aggregations: {
my_scoped_agg: {
terms: {
field: 'http.response.status_code'
}
},
my_global_agg: {
global: {},
aggregations: {
my_level_agg: {
terms: {
field: 'http.response.status_code'
}
}
}
}
},
post_filter: {
match: {
message: 'search'
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
profile: true,
query: {
term: {
"user.id": {
value: "elkbee",
},
},
},
aggs: {
my_scoped_agg: {
terms: {
field: "http.response.status_code",
},
},
my_global_agg: {
global: {},
aggs: {
my_level_agg: {
terms: {
field: "http.response.status_code",
},
},
},
},
},
post_filter: {
match: {
message: "search",
},
},
});
console.log(response);
Console
GET /my-index-000001/_search
{
"profile": true,
"query": {
"term": {
"user.id": {
"value": "elkbee"
}
}
},
"aggs": {
"my_scoped_agg": {
"terms": {
"field": "http.response.status_code"
}
},
"my_global_agg": {
"global": {},
"aggs": {
"my_level_agg": {
"terms": {
"field": "http.response.status_code"
}
}
}
}
},
"post_filter": {
"match": {
"message": "search"
}
}
}
この例には:
- クエリ
- スコープ付き集約
- グローバル集約
- ポストフィルター
Console-Result
{
...
"profile": {
"shards": [
{
"id": "[P6xvulHtQRWuD4YnubWb7A][my-index-000001][0]",
"node_id": "P6xvulHtQRWuD4YnubWb7A",
"shard_id": 0,
"index": "my-index-000001",
"cluster": "(local)",
"searches": [
{
"query": [
{
"type": "TermQuery",
"description": "message:search",
"time_in_nanos": 141618,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 0,
"set_min_competitive_score": 0,
"next_doc": 0,
"match": 0,
"next_doc_count": 0,
"score_count": 0,
"compute_max_score_count": 0,
"compute_max_score": 0,
"advance": 3942,
"advance_count": 4,
"count_weight_count": 0,
"score": 0,
"build_scorer_count": 2,
"create_weight": 38380,
"shallow_advance": 0,
"count_weight": 0,
"create_weight_count": 1,
"build_scorer": 99296
}
},
{
"type": "TermQuery",
"description": "user.id:elkbee",
"time_in_nanos": 163081,
"breakdown": {
"set_min_competitive_score_count": 0,
"match_count": 0,
"shallow_advance_count": 0,
"set_min_competitive_score": 0,
"next_doc": 2447,
"match": 0,
"next_doc_count": 4,
"score_count": 4,
"compute_max_score_count": 0,
"compute_max_score": 0,
"advance": 3552,
"advance_count": 1,
"score": 5027,
"count_weight_count": 0,
"build_scorer_count": 2,
"create_weight": 107840,
"shallow_advance": 0,
"count_weight": 0,
"create_weight_count": 1,
"build_scorer": 44215
}
}
],
"rewrite_time": 4769,
"collector": [
{
"name": "QueryPhaseCollector",
"reason": "search_query_phase",
"time_in_nanos": 1945072,
"children": [
{
"name": "SimpleTopScoreDocCollector",
"reason": "search_top_hits",
"time_in_nanos": 22577
},
{
"name": "AggregatorCollector: [my_scoped_agg, my_global_agg]",
"reason": "aggregation",
"time_in_nanos": 867617
}
]
}
]
}
],
"aggregations": [...],
"fetch": {...}
}
]
}
}
"aggregations" 部分は省略されています。次のセクションでカバーされます。 |
ご覧のとおり、出力は以前よりもかなり冗長です。クエリの主要な部分がすべて表されています:
- 1. 最初の
TermQuery
(user.id:elkbee)は、メインのterm
クエリを表します。 - 2. 2番目の
TermQuery
(message:search)は、post_filter
クエリを表します。
コレクターツリーは非常に簡単で、通常のスコアリングSimpleTopScoreDocCollectorを保持する単一のQueryPhaseCollectorと、すべてのスコープ付き集約を実行するBucketCollectorWrapperを示しています。
Understanding MultiTermQuery output
本質的に、これらのクエリはセグメントごとに自分自身をリライトします。ワイルドカードクエリ`````b*`````を想像してみてください。これは、技術的には「b」文字で始まる任意のトークンに一致する可能性があります。すべての可能な組み合わせを列挙することは不可能なので、Luceneは評価されているセグメントのコンテキストでクエリをリライトします。たとえば、1つのセグメントには`````[bar, baz]`````トークンが含まれている可能性があるため、クエリは「bar」と「baz」のBooleanQueryの組み合わせにリライトされます。別のセグメントには`````[bakery]`````トークンのみが含まれている可能性があるため、クエリは「bakery」の単一のTermQueryにリライトされます。
この動的なセグメントごとのリライトにより、クリーンなツリー構造が歪み、1つのクエリが次のクエリにどのようにリライトされるかを示すクリーンな「系譜」をもはや追跡できなくなります。現時点では、すべての詳細が混乱しすぎる場合は、そのクエリの子の詳細を折りたたむことをお勧めします。幸いなことに、すべてのタイミング統計は正確であり、レスポンスの物理的なレイアウトが正しくないだけなので、トップレベルのMultiTermQueryを分析し、その詳細が解釈するには難しすぎる場合は子を無視するだけで十分です。
将来的にはこの問題が解決されることを願っていますが、解決が難しい問題であり、まだ進行中です。 :)
### Profiling Aggregations
#### aggregations Section
`````aggregations`````セクションには、特定のシャードで実行された集約ツリーの詳細なタイミングが含まれています。この集約ツリーの全体的な構造は、元のElasticsearchリクエストに似ています。前のクエリを再度実行し、今回は集約プロファイルを見てみましょう:
#### Python
``````python
resp = client.search(
index="my-index-000001",
profile=True,
query={
"term": {
"user.id": {
"value": "elkbee"
}
}
},
aggs={
"my_scoped_agg": {
"terms": {
"field": "http.response.status_code"
}
},
"my_global_agg": {
"global": {},
"aggs": {
"my_level_agg": {
"terms": {
"field": "http.response.status_code"
}
}
}
}
},
post_filter={
"match": {
"message": "search"
}
},
)
print(resp)
`
Ruby
response = client.search(
index: 'my-index-000001',
body: {
profile: true,
query: {
term: {
'user.id' => {
value: 'elkbee'
}
}
},
aggregations: {
my_scoped_agg: {
terms: {
field: 'http.response.status_code'
}
},
my_global_agg: {
global: {},
aggregations: {
my_level_agg: {
terms: {
field: 'http.response.status_code'
}
}
}
}
},
post_filter: {
match: {
message: 'search'
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
profile: true,
query: {
term: {
"user.id": {
value: "elkbee",
},
},
},
aggs: {
my_scoped_agg: {
terms: {
field: "http.response.status_code",
},
},
my_global_agg: {
global: {},
aggs: {
my_level_agg: {
terms: {
field: "http.response.status_code",
},
},
},
},
},
post_filter: {
match: {
message: "search",
},
},
});
console.log(response);
Console
GET /my-index-000001/_search
{
"profile": true,
"query": {
"term": {
"user.id": {
"value": "elkbee"
}
}
},
"aggs": {
"my_scoped_agg": {
"terms": {
"field": "http.response.status_code"
}
},
"my_global_agg": {
"global": {},
"aggs": {
"my_level_agg": {
"terms": {
"field": "http.response.status_code"
}
}
}
}
},
"post_filter": {
"match": {
"message": "search"
}
}
}
Console-Result
{
"profile": {
"shards": [
{
"aggregations": [
{
"type": "NumericTermsAggregator",
"description": "my_scoped_agg",
"time_in_nanos": 79294,
"breakdown": {
"reduce": 0,
"build_aggregation": 30885,
"build_aggregation_count": 1,
"initialize": 2623,
"initialize_count": 1,
"reduce_count": 0,
"collect": 45786,
"collect_count": 4,
"build_leaf_collector": 18211,
"build_leaf_collector_count": 1,
"post_collection": 929,
"post_collection_count": 1
},
"debug": {
"total_buckets": 1,
"result_strategy": "long_terms",
"built_buckets": 1
}
},
{
"type": "GlobalAggregator",
"description": "my_global_agg",
"time_in_nanos": 104325,
"breakdown": {
"reduce": 0,
"build_aggregation": 22470,
"build_aggregation_count": 1,
"initialize": 12454,
"initialize_count": 1,
"reduce_count": 0,
"collect": 69401,
"collect_count": 4,
"build_leaf_collector": 8150,
"build_leaf_collector_count": 1,
"post_collection": 1584,
"post_collection_count": 1
},
"debug": {
"built_buckets": 1
},
"children": [
{
"type": "NumericTermsAggregator",
"description": "my_level_agg",
"time_in_nanos": 76876,
"breakdown": {
"reduce": 0,
"build_aggregation": 13824,
"build_aggregation_count": 1,
"initialize": 1441,
"initialize_count": 1,
"reduce_count": 0,
"collect": 61611,
"collect_count": 4,
"build_leaf_collector": 5564,
"build_leaf_collector_count": 1,
"post_collection": 471,
"post_collection_count": 1
},
"debug": {
"total_buckets": 1,
"result_strategy": "long_terms",
"built_buckets": 1
}
}
]
}
]
}
]
}
}
プロファイル構造から、my_scoped_agg
がNumericTermsAggregator
(集約しているフィールドhttp.response.status_code
が数値フィールドであるため)として内部的に実行されていることがわかります。同じレベルで、GlobalAggregator
がmy_global_agg
から来ているのが見えます。その集約には、NumericTermsAggregator
の2番目の項目の集約から来る子NumericTermsAggregator
があります。
一部の集約は、集約の基盤となる実行の特徴を説明する専門的な`````debug`````情報を返す場合があります。これは、集約に取り組む人々にとって「役立つ」ものであり、他の人には役立たないと期待されるものです。これらはバージョン、集約、および集約実行戦略によって大きく異なる可能性があります。
### Timing Breakdown
`````breakdown`````コンポーネントは、低レベルの実行に関する詳細な統計をリストします:
#### Js
``````js
"breakdown": {
"reduce": 0,
"build_aggregation": 30885,
"build_aggregation_count": 1,
"initialize": 2623,
"initialize_count": 1,
"reduce_count": 0,
"collect": 45786,
"collect_count": 4,
"build_leaf_collector": 18211,
"build_leaf_collector_count": 1
}
`
breakdown
コンポーネント内の各プロパティは、集約の内部メソッドに対応しています。たとえば、build_leaf_collector
プロパティは、集約のgetLeafCollector()
メソッドを実行するのにかかったナノ秒を測定します。_count
で終わるプロパティは、特定のメソッドの呼び出し回数を記録します。たとえば、"collect_count": 2
は、集約が2つの異なるドキュメントでcollect()
を呼び出したことを意味します。reduce
プロパティは将来の使用のために予約されており、常に0
を返します。
タイミングは壁時計のナノ秒でリストされ、全く正規化されていません。全体のtime
に関するすべての注意事項がここに適用されます。内訳の意図は、A) Elasticsearchのどの機械が実際に時間を消費しているか、B) 様々なコンポーネント間の時間の違いの大きさを感じ取ることです。全体の時間と同様に、内訳はすべての子の時間を含みます。
Profiling Fetch
ドキュメントを取得したすべてのシャードには、プロファイル内にfetch
セクションがあります。小さな検索を実行し、フェッチプロファイルを見てみましょう:
Python
resp = client.search(
index="my-index-000001",
filter_path="profile.shards.fetch",
profile=True,
query={
"term": {
"user.id": {
"value": "elkbee"
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
filter_path: 'profile.shards.fetch',
body: {
profile: true,
query: {
term: {
'user.id' => {
value: 'elkbee'
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
filter_path: "profile.shards.fetch",
profile: true,
query: {
term: {
"user.id": {
value: "elkbee",
},
},
},
});
console.log(response);
Console
GET /my-index-000001/_search?filter_path=profile.shards.fetch
{
"profile": true,
"query": {
"term": {
"user.id": {
"value": "elkbee"
}
}
}
}
Console-Result
{
"profile": {
"shards": [
{
"fetch": {
"type": "fetch",
"description": "",
"time_in_nanos": 660555,
"breakdown": {
"next_reader": 7292,
"next_reader_count": 1,
"load_stored_fields": 299325,
"load_stored_fields_count": 5,
"load_source": 3863,
"load_source_count": 5
},
"debug": {
"stored_fields": ["_id", "_routing", "_source"]
},
"children": [
{
"type" : "FetchFieldsPhase",
"description" : "",
"time_in_nanos" : 238762,
"breakdown" : {
"process_count" : 5,
"process" : 227914,
"next_reader" : 10848,
"next_reader_count" : 1
}
},
{
"type": "FetchSourcePhase",
"description": "",
"time_in_nanos": 20443,
"breakdown": {
"next_reader": 745,
"next_reader_count": 1,
"process": 19698,
"process_count": 5
},
"debug": {
"fast_path": 4
}
},
{
"type": "StoredFieldsPhase",
"description": "",
"time_in_nanos": 5310,
"breakdown": {
"next_reader": 745,
"next_reader_count": 1,
"process": 4445,
"process_count": 5
}
}
]
}
}
]
}
}
これは、Elasticsearchがフェッチを実行する方法に関するデバッグ情報であり、リクエストごとやバージョンごとに変わる可能性があります。パッチバージョンでさえ、ここでの出力が変わる可能性があります。この一貫性の欠如がデバッグに役立つ理由です。
とにかく!time_in_nanos
はフェッチフェーズの合計時間を測定します。breakdown
は、next_reader
での各セグメントの準備のカウントと時間、およびload_stored_fields
での保存されたフィールドの読み込みにかかる時間を測定します。Debugには、特にstored_fields
がフェッチが読み込む必要がある保存されたフィールドのリストを示す、さまざまな非タイミング情報が含まれています。リストが空である場合、フェッチは保存されたフィールドの読み込みを完全にスキップします。
私たちは、フェッチに必要なすべての保存されたフィールドを前もって読み込むように努めています。これにより、`````_source`````フェーズはヒットごとに数マイクロ秒になります。この場合、`````_source`````フェーズの真のコストは、内訳の`````load_stored_fields`````コンポーネントに隠れています。`````"_source": false, "stored_fields": ["_none_"]`````を設定することで、保存されたフィールドの読み込みを完全にスキップすることが可能です。
### Profiling DFS
DFSフェーズは、クエリフェーズの前に実行され、クエリに関連するグローバル情報を収集します。現在、これは2つのケースで使用されています:
- 1*.* `````search_type`````が[`````dfs_query_then_fetch`````](237e89fe5c1c5ae9.md#profiling-dfs-statistics)に設定され、インデックスに複数のシャードがある場合。
- 2*.* 検索リクエストに[knnセクション](237e89fe5c1c5ae9.md#profiling-knn-search)が含まれている場合。
これらのケースの両方は、検索リクエストの一部として`````profile`````を`````true`````に設定することでプロファイルできます。
#### Profiling DFS Statistics
`````search_type`````が`````dfs_query_then_fetch`````に設定され、インデックスに複数のシャードがある場合、dfsフェーズは検索結果の関連性を向上させるために用語統計を収集します。
次の例は、`````profile`````を`````true`````に設定した検索の例です:
まず、複数のシャードを持つインデックスを設定し、`````keyword`````フィールドに異なる値を持つ2つのドキュメントをインデックスします。
#### Python
``````python
resp = client.indices.create(
index="my-dfs-index",
settings={
"number_of_shards": 2,
"number_of_replicas": 1
},
mappings={
"properties": {
"my-keyword": {
"type": "keyword"
}
}
},
)
print(resp)
resp1 = client.bulk(
index="my-dfs-index",
refresh=True,
operations=[
{
"index": {
"_id": "1"
}
},
{
"my-keyword": "a"
},
{
"index": {
"_id": "2"
}
},
{
"my-keyword": "b"
}
],
)
print(resp1)
`
Ruby
response = client.indices.create(
index: 'my-dfs-index',
body: {
settings: {
number_of_shards: 2,
number_of_replicas: 1
},
mappings: {
properties: {
"my-keyword": {
type: 'keyword'
}
}
}
}
)
puts response
response = client.bulk(
index: 'my-dfs-index',
refresh: true,
body: [
{
index: {
_id: '1'
}
},
{
"my-keyword": 'a'
},
{
index: {
_id: '2'
}
},
{
"my-keyword": 'b'
}
]
)
puts response
Js
const response = await client.indices.create({
index: "my-dfs-index",
settings: {
number_of_shards: 2,
number_of_replicas: 1,
},
mappings: {
properties: {
"my-keyword": {
type: "keyword",
},
},
},
});
console.log(response);
const response1 = await client.bulk({
index: "my-dfs-index",
refresh: "true",
operations: [
{
index: {
_id: "1",
},
},
{
"my-keyword": "a",
},
{
index: {
_id: "2",
},
},
{
"my-keyword": "b",
},
],
});
console.log(response1);
Console
PUT my-dfs-index
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"my-keyword": { "type": "keyword" }
}
}
}
POST my-dfs-index/_bulk?refresh=true
{ "index" : { "_id" : "1" } }
{ "my-keyword" : "a" }
{ "index" : { "_id" : "2" } }
{ "my-keyword" : "b" }
my-dfs-index は複数のシャードで作成されます。 |
インデックスが設定されたので、検索クエリのdfsフェーズをプロファイルできます。この例では、用語クエリを使用します。
Python
resp = client.search(
index="my-dfs-index",
search_type="dfs_query_then_fetch",
pretty=True,
size="0",
profile=True,
query={
"term": {
"my-keyword": {
"value": "a"
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-dfs-index',
search_type: 'dfs_query_then_fetch',
pretty: true,
size: 0,
body: {
profile: true,
query: {
term: {
"my-keyword": {
value: 'a'
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-dfs-index",
search_type: "dfs_query_then_fetch",
pretty: "true",
size: 0,
profile: true,
query: {
term: {
"my-keyword": {
value: "a",
},
},
},
});
console.log(response);
Console
GET /my-dfs-index/_search?search_type=dfs_query_then_fetch&pretty&size=0
{
"profile": true,
"query": {
"term": {
"my-keyword": {
"value": "a"
}
}
}
}
search_type URLパラメータはdfs_query_then_fetch に設定され、dfsフェーズが実行されることを保証します。 |
|
profile パラメータはtrue に設定されます。 |
レスポンスでは、各シャードのdfs
セクションと、検索フェーズの残りのプロファイル出力が含まれたプロファイルが表示されます。シャードのdfs
セクションの1つは次のようになります:
Console-Result
"dfs" : {
"statistics" : {
"type" : "statistics",
"description" : "collect term statistics",
"time_in_nanos" : 236955,
"breakdown" : {
"term_statistics" : 4815,
"collection_statistics" : 27081,
"collection_statistics_count" : 1,
"create_weight" : 153278,
"term_statistics_count" : 1,
"rewrite_count" : 0,
"create_weight_count" : 1,
"rewrite" : 0
}
}
}
このレスポンスのdfs.statistics
部分には、用語統計を収集するのにかかった合計時間を示すtime_in_nanos
が含まれており、個々の部分のさらなる内訳が表示されます。
Profiling kNN Search
k近傍(kNN)検索は、dfsフェーズ中に実行されます。
次の例は、profile
をtrue
に設定した検索の例です:
まず、いくつかの密なベクトルを持つインデックスを設定します。
Python
resp = client.indices.create(
index="my-knn-index",
mappings={
"properties": {
"my-vector": {
"type": "dense_vector",
"dims": 3,
"index": True,
"similarity": "l2_norm"
}
}
},
)
print(resp)
resp1 = client.bulk(
index="my-knn-index",
refresh=True,
operations=[
{
"index": {
"_id": "1"
}
},
{
"my-vector": [
1,
5,
-20
]
},
{
"index": {
"_id": "2"
}
},
{
"my-vector": [
42,
8,
-15
]
},
{
"index": {
"_id": "3"
}
},
{
"my-vector": [
15,
11,
23
]
}
],
)
print(resp1)
Ruby
response = client.indices.create(
index: 'my-knn-index',
body: {
mappings: {
properties: {
"my-vector": {
type: 'dense_vector',
dims: 3,
index: true,
similarity: 'l2_norm'
}
}
}
}
)
puts response
response = client.bulk(
index: 'my-knn-index',
refresh: true,
body: [
{
index: {
_id: '1'
}
},
{
"my-vector": [
1,
5,
-20
]
},
{
index: {
_id: '2'
}
},
{
"my-vector": [
42,
8,
-15
]
},
{
index: {
_id: '3'
}
},
{
"my-vector": [
15,
11,
23
]
}
]
)
puts response
Js
const response = await client.indices.create({
index: "my-knn-index",
mappings: {
properties: {
"my-vector": {
type: "dense_vector",
dims: 3,
index: true,
similarity: "l2_norm",
},
},
},
});
console.log(response);
const response1 = await client.bulk({
index: "my-knn-index",
refresh: "true",
operations: [
{
index: {
_id: "1",
},
},
{
"my-vector": [1, 5, -20],
},
{
index: {
_id: "2",
},
},
{
"my-vector": [42, 8, -15],
},
{
index: {
_id: "3",
},
},
{
"my-vector": [15, 11, 23],
},
],
});
console.log(response1);
Console
PUT my-knn-index
{
"mappings": {
"properties": {
"my-vector": {
"type": "dense_vector",
"dims": 3,
"index": true,
"similarity": "l2_norm"
}
}
}
}
POST my-knn-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "my-vector": [1, 5, -20] }
{ "index": { "_id": "2" } }
{ "my-vector": [42, 8, -15] }
{ "index": { "_id": "3" } }
{ "my-vector": [15, 11, 23] }
インデックスが設定されたので、kNN検索クエリをプロファイルできます。
Python
resp = client.search(
index="my-knn-index",
profile=True,
knn={
"field": "my-vector",
"query_vector": [
-5,
9,
-12
],
"k": 3,
"num_candidates": 100
},
)
print(resp)
Ruby
response = client.search(
index: 'my-knn-index',
body: {
profile: true,
knn: {
field: 'my-vector',
query_vector: [
-5,
9,
-12
],
k: 3,
num_candidates: 100
}
}
)
puts response
Js
const response = await client.search({
index: "my-knn-index",
profile: true,
knn: {
field: "my-vector",
query_vector: [-5, 9, -12],
k: 3,
num_candidates: 100,
},
});
console.log(response);
Console
POST my-knn-index/_search
{
"profile": true,
"knn": {
"field": "my-vector",
"query_vector": [-5, 9, -12],
"k": 3,
"num_candidates": 100
}
}
profile パラメータはtrue に設定されます。 |
レスポンスでは、各シャードのknn
セクションがdfs
セクションの一部として表示され、検索フェーズの残りのプロファイル出力が含まれます。
シャードのdfs.knn
セクションの1つは次のようになります:
Js
"dfs" : {
"knn" : [
{
"vector_operations_count" : 4,
"query" : [
{
"type" : "DocAndScoreQuery",
"description" : "DocAndScore[100]",
"time_in_nanos" : 444414,
"breakdown" : {
"set_min_competitive_score_count" : 0,
"match_count" : 0,
"shallow_advance_count" : 0,
"set_min_competitive_score" : 0,
"next_doc" : 1688,
"match" : 0,
"next_doc_count" : 3,
"score_count" : 3,
"compute_max_score_count" : 0,
"compute_max_score" : 0,
"advance" : 4153,
"advance_count" : 1,
"score" : 2099,
"build_scorer_count" : 2,
"create_weight" : 128879,
"shallow_advance" : 0,
"create_weight_count" : 1,
"build_scorer" : 307595,
"count_weight": 0,
"count_weight_count": 0
}
}
],
"rewrite_time" : 1275732,
"collector" : [
{
"name" : "SimpleTopScoreDocCollector",
"reason" : "search_top_hits",
"time_in_nanos" : 17163
}
]
} ]
}
レスポンスのdfs.knn
部分では、クエリ、リライト、およびコレクターのタイミング出力が表示されます。他の多くのクエリとは異なり、kNN検索はクエリリライト中に大部分の作業を行います。これは、rewrite_time
がkNN検索に費やされた時間を表すことを意味します。属性vector_operations_count
は、kNN検索中に実行されたベクトル操作の全体的なカウントを表します。
プロファイリングの考慮事項
任意のプロファイラと同様に、Profile APIは検索実行に対して無視できないオーバーヘッドを導入します。collect
、advance
、およびnext_doc
のような低レベルのメソッド呼び出しを計測する行為は、これらのメソッドがタイトなループで呼び出されるため、かなり高価になる可能性があります。したがって、プロファイリングはデフォルトで本番環境では有効にすべきではなく、非プロファイルのクエリ時間と比較すべきではありません。プロファイリングは単なる診断ツールです。
また、プロファイリングに適さないために特別なLuceneの最適化が無効になる場合もあります。これにより、一部のクエリが非プロファイルの対応物よりも相対的に長い時間を報告する可能性がありますが、一般的にはプロファイルされたクエリの他のコンポーネントと比較して劇的な影響はないはずです。
制限事項
- 現在、プロファイリングはネットワークオーバーヘッドを測定していません。
- プロファイリングは、キューでの待機時間、コーディネートノードでのシャード応答のマージ、または検索を高速化するために使用される内部データ構造であるグローバルオーディナルの構築などの追加作業にかかる時間を考慮していません。
- 現在、提案に対するプロファイリング統計は利用できません。
- 集約のリデュースフェーズのプロファイリングは現在利用できません。
- プロファイラは、バージョンごとに変更される可能性のある内部を計測しています。その結果得られるjsonは主に不安定であると見なすべきであり、特に
debug
セクションの内容については注意が必要です。