- クエリーのFROM句で指定されたイベントがインジェストされます。
- WHERE句で指定された条件に該当するデータのみが次に進みます(filter()内のWHERE句は含みません)
- FACET句で指定された属性の値ごとに振り分けられます。
- Streaming methodの設定に従い、集計窓がクローズされる
- 集計窓に入ったデータにより集計される
filter()関数は集計関数で利用できるため上記5つ目のステップのときに動作します。そのため3つ目を超えて伝搬されるデータがある場合にのみ、NULLの代わりに0を集計結果として得ることができるようになっています。
具体的な例
ProcessSampleを使った例で簡単に説明します。例えば、myProcess1というプロセス名のプロセス数を監視して、数が0になったらアラートを発砲する場合、以下のようなクエリーが記載されるかと思います。
count(*) FROM ProcessSample WHERE processDisplayName = ‘myProcess1’ FACET hostname
こちらのクエリーの場合、以下のようになります。
- ProcessSampleがインジェストされる
- WHERE句記載の条件(processDisplayName = ‘myProcess1’)に該当するデータのみが次のステップに送られる
- FACET句で指定された属性(hostname)の値ごとに振り分けられます。
- Streaming methodの設定に従い、集計窓がクローズされる
- 集計窓に入ったデータにより集計される
前述のcount(*)がゼロのケース(processDisplayName = ‘myProcess1’の条件に該当するデータがない)の場合、この3のステップに進むデータの個数がゼロ件になります。つまりステップ5に送られるデータがないため、集計結果もNULLになります。
この回避策としてfilter()が利用できる場合があります。
実際の例を以下に記載します。
filter(count(*), WHERE processDisplayName = ‘myProcess1’) FROM ProcessSample FACET hostname
前述のWHERE句で記載したprocessDisplayNameの条件をfilter関数内に移動しています。これをすることで、処理は以下のように変わります。
- ProcessSampleがインジェストされる
- WHERE句の条件がないのですべてのProcessSampleが次に進む
- FACET句で指定された属性(hostname)の値ごとに振り分けられます。
- Streaming methodの設定に従い、集計窓がクローズされる
- 集計窓に入ったデータにより集計される
処理フローとしてはステップ2の条件が変わったのみになります。これで、processDisplayName = ‘myProcess’の数がゼロになった場合にNULLではなく必ずゼロになると思った方はすでに罠にハマっています。
大事なのは、ステップ5に進むデータに何が含まれているかになります。複数のプロセスの情報がインジェストされており、processDisplayName が‘myProcess1以外のデータが存在する場合は、ステップ5に到達するデータが存在するので、集計が行われ結果がゼロになります(myProcess2やmyProcess3など含む複数のデータの中から、myProcess1のデータの個数が集計される)。そもそも、myProcess1しかInfra agentで取得していない場合、filter()を使わないケース同様、ステップ5に入ってくるデータがなくなるので、結果がNULLということには変わりません。
filter()を使えばNULLが必ずゼロになるというわけではない!
上記の通り、ステップ5でfilter()に記載の条件に該当しないデータが入るかを考慮する必要があること、必ず意識して作成してください。
よくハマる罠
1. WHERE句と同じ条件を書く
filter(count(*), WHERE processDisplayName = ‘myProcess1’) FROM ProcessSample WHERE processDisplayName = ‘myProcess1’ FACET hostname
こちら、WHERE句とfilter()で全く同じ条件を書いています。
前述の内容をよく理解できている方であればすぐにおわかりいただけると思いますが、前述のステップ2でprocessDisplayName = ‘myProcess1’のみのデータが伝搬されています。なので、processDisplayName = ‘myProcess1’の数がゼロのケースは、ステップ5に伝搬されるデータが存在しないので、filter()を使った意味がなくなっています。
2.FACET句で同じ属性を指定する
filter(count(*), WHERE processDisplayName = ‘myProcess1’) FROM ProcessSample FACET hostname, processDisplayName
こちら、アラート通知に伝搬したいのでFACET句に対象の属性を指定されてハマるケースが多いと思います。上記の場合、FACET句にprocessDisplayNameを指定しているので、ステップ3の時点で、processDisplayName毎にデータが振り分けられます。そのため、ステップ5の時点で、processDisplayName毎に集計(‘myProcess1’のみなど各値毎のみで)で集計という処理になるので、期待しない動作になります。上記クエリーの場合、myProcess1では、filter()を使わないときと同じ結果になります。myProcess2など他の値のデータがインジェストされた場合は、そのシグナルに対する集計結果は常にゼロとなります。
3.WHERE句/FACET句での条件をゆるくしすぎる
filter()をより動作させるため、FACETもWHEREも指定せず、以下のように集計対象を広く取る場合があります。
filter(count(*), WHERE processDisplayName = ‘myProcess1’) FROM ProcessSample
この場合、複数のホストからのデータをまとめて同じ集計窓に入れることでNULLをより回避しようとしていると思われます。ここで注意していただきたいのが、複数のentityからのデータを同一に扱うことのリスクです。アラートコンディションではStreaming methodでevent timerやevent flowを使ってデータのtimestampに基づき集計窓がクローズされます。送信元が別のホストなど異なる場合、timestampが前後したデータが入ってくるリスクがあります。その前後したtimestampにより、集計窓が期待しないタイミングでクローズされる可能性もあるので、複数のentityからのデータをひとまとめにしないことをおすすめします。基本的にはtimestampの前後が発生するリスクが発生しにくいグループで振り分けが行われることを意識してください。
4.必ずNULLのケースはある
前述の通り、WHERE句に該当するデータが無ければ、NULLになることはありえます。例えば、前述の例でも複数プロセスのProcessSampleがインジェストされているのでNULLにはならないと考えるのは過信です。ホストが止まったケースなどすべてのProcessSampleが止まるケースなどもあります。そういったケースはNULLになります。filter()を使った場合でもNULLになることは必ずあることは必ず意識した設計を行っていただくことをおすすめします。
まとめ
filter()を使ってNULL回避するための重要なポイントは以下のとおりです。
- NULL回避のためには、filter()に記載の条件に該当しないデータが必要。
- filer()で使った属性をアウター側のWHERE句やFACET句で利用すると期待しない動作になるリスクがある。
- NULL回避のため、データ取得の範囲を広げすぎると、timestampの前後問題により集計窓が期待せずクローズされるリスクがある。
- filter()を使った場合でも結果がNULLになるユースケースは残る。
以上を理解された上で、正しくご利用ください。
本ブログに掲載されている見解は著者に所属するものであり、必ずしも New Relic 株式会社の公式見解であるわけではありません。また、本ブログには、外部サイトにアクセスするリンクが含まれる場合があります。それらリンク先の内容について、New Relic がいかなる保証も提供することはありません。