KibanaのProfilerでElasticsearchのクエリを分析する

KibanaのDev Toolsの一つに[Profiler](https://www.elastic.co/guide/en/kibana/current/xpack-profiler.html)という機能があります。
このProfilerを利用すると検索や集計のクエリを分析し、どんなクエリが発行されたのか、どのくらいの処理時間がかかっているのか等を簡単に知ることが出来ます。
内部的にはElasticsearchの[Profiler API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-profile.html)を利用しているみたいですが、このAPIのレスポンス内容から分析するのは中々大変なのでKibanaを利用した方がお手軽です。

ネットワークのオーバーヘッドやキューの待ち時間等、Profilerで計測出来ない値も色々あるのですが、それでも手探りであれこれ試すよりはProfilerを利用した方が色々と便利かなと思います。
（※参考：[Profileの制限](https://www.elastic.co/guide/en/elasticsearch/reference/current/_profiling_considerations.html#profile-limitations)）

Profilerは Dev Tools > Search Profiler から開くことが出来ます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/2df5d7b2-4c9c-9451-acde-e77626cdd83e.png)


# 環境
Elasticsearch 6.5.4
Kibana 6.5.4

# サンプルデータ
以下のように、日付、URL、ステータスコード、レスポンス時間を持っている簡素なサーバーログっぽいものを用意しました。

```json
{
    "date": "2019-01-14T00:01:00",
    "url": "/test/huga/",
    "status_code": 200,
    "response_time": 591
},
{
    "date": "2019-01-14T00:02:00",
    "url": "/test/hoge/",
    "status_code": 404,
    "response_time": 632
},
{
    "date": "2019-01-14T00:03:00",
    "url": "/test/piyo/",
    "status_code": 500,
    "response_time": 1237
}
```

# 検索クエリの分析
Profilerの使い方は簡単で、分析したい検索クエリをそのまま書いて「Profile」ボタンを押すだけです。
結果は`Query Profile`タブに表示されます。
検索対象のIndexを指定したい場合は`Index`欄で指定してください。
（Typeを指定したい場合は`Type`欄で指定できます。ただTypeはElastic Stack 7系以降で廃止予定ですので、使わなくても大丈夫な状態にしておいた方が良いかなと思います。）

試しに「test-access-log」から始まるIndexに対し、status_codeが200のドキュメントを検索するクエリを分析してみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/016ae87f-23e5-a99d-a6c4-923ef93ab5ef.png)
実行すると、test-access-logとtest-access-log-2というIndexが検索対象となったことがわかりました。
test-access-logはShard数を1、test-access-log-2はShard数を3に設定しているため、各Shardに対して検索が実行されているということもわかります。

また、`Cumulative Time`を見ることで、このクエリを処理するためにかかった各Shardの処理時間の合計値がわかります。
（※各Shardの処理は並列に実行されるため、`Cumulative Time`は検索してからレスポンスを得るまでの実行時間とは異なる、という点に注意してください）
例えば同じ結果が返ってくるクエリを複数のパターンで書くことが出来る場合、この値を比べることでElasticsearch上の処理時間についての参考指標にすることが出来ます。

## 発行されたクエリの詳細を確認する
「>」を押すと詳細が展開され、実際にどのようなクエリが発行されていたのかを知ることが出来ます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/185522d8-bf6d-7d83-0157-2f0e105e11dc.png)
これを見ると、今回実行したクエリはApache LuceneのPointRangeQueryに変換されて発行されているようです。
`status_code:[200 TO 200]`となっているので、200～200で検索しているんだろうなというのがわかります。

実際に発行されたクエリの中身を確認できるのが結構便利で、例えばnowを利用して現在時刻を基準に検索を行った場合に、実際には何時で検索されたのかを知りたい場合にも役立ちます。
以下の例では現在時刻を基準にdateが昨日の12時から今日の12時のドキュメントを検索しようとしています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/86b13c9c-09eb-bb10-d6de-3377ba214a26.png)
クエリを書いている最中は`now-1d/d+12h`で昨日の12時がうまく指定できているかどうかちょっと不安になりますが、Profilerを使うことでエポックミリ秒の1547348400000で検索出来ていることがわかります。
（例では2019/01/14に実行したので、1547348400000 -> 2019/01/13 12:00:00.000 になっています）

また、右側の`Timing Breakdown`には処理時間の内訳が表示されています。
ただ、ここの値を自分で調整するのは中々難しいのかなと思いますので、参考程度に見ておけば良さそうです。

|項目|意味|
|:------|:------|
|advance|イテレーターが次のドキュメントを取得する時間|
|build_scorer|スコア計算用のオブジェクト生成にかかった時間|
|create_weight|weightを保持するためのオブジェクト生成にかかった時間|
|match|phraseクエリを使用した際にスコアリングに要した時間|
|next_doc|イテレーターがマッチした次のドキュメントを取得する時間|
|score|ドキュメントのスコアリングに要した時間|

（※参考：[Query ProfileのTiming Breakdownの項目の詳細](https://www.elastic.co/guide/en/elasticsearch/reference/current/_profiling_queries.html#_all_parameters_3)）

# 集計クエリの分析
集計クエリの分析も検索と同様にクエリをそのまま書いて「Profile」ボタンを押すだけです。
結果は`Aggregation Profile`タブに表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/9ee78b76-5f1a-3bce-da49-ee88459fe8a7.png)
これを見ると、今回実行したクエリの内、status_code_aggsの方はLongTermsAggregatorクラスを利用して集計されているというようなことがわかります。

パフォーマンスチューニングを行う場合、内部的に利用されているクラスを元に色々と調整していくのは中々難しそうです。
ですので、各集計の処理時間を元に時間がかかっている集計を特定し、集計条件の見直しやIndexに持たせるFieldの調整等を行っていくのがよさそうです。

|項目|意味|
|:------|:------|
|build_aggregation|収集したドキュメントを元にした集計処理にかかった時間|
|collect|条件にマッチするドキュメントの収集にかかった時間|
|initialize|集計開始前の初期化処理にかかった時間|
|reduce|現在利用されておらず常に0|

（※参考：[Aggregation ProfileのTiming Breakdownの項目の詳細](https://www.elastic.co/guide/en/elasticsearch/reference/current/_profiling_aggregations.html#_all_parameters_4)）