良いクエリとは
Kusto において、良いクエリとは以下の2つに分類できると思います。
- 処理が高速
- 読みやすい(メンテナンスしやすい)
処理が高速とは
Kusto は主に何かの調査をするときに使うと思います。調査はなるべく早く終わらせて、次のアクションに移らなければならない、そんなときにクエリの実行に時間がかかっていては仕事が捗りません。なるべく高速に動くクエリを書くことが求められます。
読みやすいとは
クエリの管理を自分ひとりが行っていればよいですが、多くの場合、会社チームの複数メンバーで管理すると思います。そのクエリが何をするためのクエリなのか分かりづらいと、クエリのメンテナンスがしづらいです。自分以外の誰が見ても分かりやすいクエリを書くことが重要です。
処理が高速なクエリを書くには
Microsoft の公式ドキュメントに、Kusto ベストプラクティス があります。こちらを参考にしながらクエリを書くことがおすすめです。まずは、これに則った書き方ができれば、おおかたのスピードに関する問題は解決できると思います。
クエリのボリュームによりますが、私は普段、最低でも1分以内に結果を返してくれるクエリを書くことを意識しています。
where 句でなるべく検索ボリュームを減らす、contains ではなく has を使う、project で検索フィールドを限定してあげる、などは良く使う手法です。
以下にいくつか例をあげます。
クエリ例1
クエリの検索ボリュームを減らすことはダイレクトに処理の高速化につながります。
以下のクエリは、過去3時間分のログを取得しています。過去どれくらいの期間のログを確認したいかおおよそのボリュームが把握できているのであれば、時間に関するフィールド(今回の場合 TimeGenerated)などで抽出可能なので、必ず行いましょう。
MyTable
| whete TimeGenerated > ago(3h)
クエリ例2
表示するフィールドを限定することも処理の高速化につながります。
以下のクエリは、project で表示したいフィールドを限定しています。project を使用しない場合、検索したいテーブルに格納されているフィールドの種類が多ければ多いほど処理に時間がかかってしまいます。確認したいフィールドが決まっている場合はフィールドを指定するのがおすすめです。
MyTable
| project City, SessionId, StartTime, StopTime
読みやすいクエリを書くには
以下の2点が、読みやすいクエリを書くときに意識していることです。
- 定義する
- 分割する
Kusto において、 extend や let を使うことで、新たな変数や一連の処理を定義することができます。これらをうまく使うと、クエリを綺麗に整理でき読みやすくできます。
例を2つ示して、それぞれポイントを解説していこうと思います。
クエリ例 1
少し処理が複雑なクエリですが、extend や let を使っている行に注目してみましょう。以下がポイントになるかと思います。
- クエリ内で何度も使う値は extend を使い、一つの変数にセットしています。(4,5,6行目)
- 1行目、2行目は時間を定義している部分ですが、ここは調査の都合によって確認したい時間帯を変更することがあるので、見やすい位置(最上部)に let で定義しています。
let _start = datetime(2018-09-24);
let _end = _start + 13d;
Fruits
| extend _bin = bin_at(Timestamp, 1d, _start)
| extend _endRange = iif(_bin + 7d > _end, _end,
iff( _bin + 7d - 1d < _start, _start,
iff( _bin + 7d - 1d < _bin, _bin, _bin + 7d - 1d)))
| extend _range = range(_bin, _endRange, 1d)
| mv-expand _range to typeof(datetime) limit 1000000
| summarize min(Price), max(Price), sum(Price) by Timestamp=bin_at(_range, 1d, _start) , Fruit
| where Timestamp >= _start + 7d;
クエリの引用元: Kusto クエリのサンプル
よろしくないクエリの例として、8行目(_range を定義している部分)にて extend で定義しない場合以下のような形になります。
8行目と9行目で同じ変数 range(_bin, _endRange, 1d) が書かれてしまっているため、可読性や保守性に欠けるクエリになってしまいます
let _start = datetime(2018-09-24);
let _end = _start + 13d;
Fruits
| extend _bin = bin_at(Timestamp, 1d, _start)
| extend _endRange = iif(_bin + 7d > _end, _end,
iff( _bin + 7d - 1d < _start, _start,
iff( _bin + 7d - 1d < _bin, _bin, _bin + 7d - 1d)))
| mv-expand range(_bin, _endRange, 1d) to typeof(datetime) limit 1000000
| summarize min(Price), max(Price), sum(Price) by Timestamp=bin_at(range(_bin, _endRange, 1d), 1d, _start) , Fruit
| where Timestamp >= _start + 7d;
Kusto ベストプラクティス にて解説されていますが、一度しか使用されない変数を extend で定義するのはクエリが複雑化する原因になります。その場合は、extend を使わずに1行で書いてしまいましょう。
T | where predicate(expression) を使用します
T | extend _value = expression | where predicate(_value) を使用しないでください
クエリ例 2
このクエリのポイントは分割を上手く活用しているところです。
1行目で1連の処理(MyLogTableの処理)を定義して、2行目と4行目で使い回しています。
初めに定義しておく処理が複雑であればあるほど、分割の効果が大きく発揮されます。
let Events = MyLogTable | where ... ;
Events
| where Name == "Start"
| project Name, City, SessionId, StartTime=timestamp
| join (Events
| where Name="Stop"
| project StopTime=timestamp, SessionId)
on SessionId
| project City, SessionId, StartTime, StopTime, Duration = StopTime - StartTime
クエリの引用元: let ステートメント
よろしくないクエリの例として、1行目で処理を定義しない場合以下のような形になります。
1行目と4行目で同じ処理(MyLogTable | where ...)が書かれてしまっているため、可読性や保守性に欠けるクエリになってしまいます。
MyLogTable | where ...
| where Name == "Start"
| project Name, City, SessionId, StartTime=timestamp
| join (MyLogTable | where ...
| where Name="Stop"
| project StopTime=timestamp, SessionId)
on SessionId
| project City, SessionId, StartTime, StopTime, Duration = StopTime - StartTime
Kusto 更なる上達に向けて
さらに良いクエリを書くためには、使える文法を増やすことが次のステップになると思います。
ただ、文法を一から学ぶよりは、分からないことが出てきたらその都度調べて解決していく方が早く上達すると思います。文法の量はとても多く、全部覚えること自体が効率的ではありません。where、extend、project、summarize など基本的な文法を自由自在に扱える状態にして、それ以外は覚えるのではなくその都度検索しながらクエリを書いていくことがコツです。
Microsoft の公式ドキュメントやスタックオーバーフローなどが役に立ちます。調べる際は英語での検索がオススメです。