Watcherで各bucketのdoc_countを集計したい

お世話になります。
異なるkeyの各bucketの件数を集計して、その件数に応じてアラートを発報したいのですが実装方法で悩んでおり、お知恵をお借りしたいです。

wacherで以下のようなaggsを作成して、stsCodeの値ごとにbucketを作成しています。

"aggs": {
  "sts_success_count": {
    "terms": {
      "field": "info.stsCode",
      "include": [
        "00000",
        "99998",
        "99999"
      ]
    }
  }
}

この結果は以下のようにレスポンスが返ってきています。

"buckets": [
  {
    "doc_count": 5966,
    "key": "00000"
  },
  {
    "doc_count": 8,
    "key": "99999"
  }
]

includeに記載しているすべてのstsCodeに対し結果が必ずしも返るわけではありません。
これらのstsCodeが示すのはいずれも正常終了のステータスなので、正常終了のステータスが9割を超えた場合にアラートが出る仕組みを作りたい、というのが今回の目標です。

試しに以下のように実装したのですが、sts_success_countに直接対応するdoc_count という要素はないのでエラーとなっています。
この箇所を、正常終了のステータスを示すstsCodeの合計件数に置き換える必要があると考えていますが、実装方法がわかりません。

"condition": {
  "script": {
    "source": "if ( ctx.payload.aggregations.sts_success_count.doc_count / ctx.payload.hits.total < params.threshold ) { return true; } return false;",
    "lang": "painless",
    "params": {
      "threshold": 0.9
    }
  }
},
"actions": {

よいやり方があれば教えていただけますと幸いです。

はじめまして。

ctx.payload.aggregations.sts_success_count.doc_countの部分がエラーの原因のようですね。できれば、エラーコード自体も貼っていただけると良いかと思います。

(inputのterms aggregationにmin_doc_count: 0を加えて全て返るようにした上で、)arrayの中のインデックスを指定して

ctx.payload.aggregations.sts_success_count.buckets.0.doc_count + ctx.payload.aggregations.sts_success_count.buckets.1.doc_count + ctx.payload.aggregations.sts_success_count.buckets.2.doc_count

とするか、

最初からterms aggregationではなく、terms queryを使ったfilter aggregationなどを用いて、inputの段階で条件を満たす文書数を合計してしまう方法もありそうです。

1 Like

アドバイスいただきありがとうございます。
アドバイスいただいた内容のうち、インデックスを指定して加算していく方法を試そうとしました。
しかしながら、こちらもエラーが出てうまくいかず、原因がわからなかったのでご確認をいただけますでしょうか?

まず、aggsは以下のように記述しています。

"aggs": {
  "sts_success_count": {
    "terms": {
      "field": "info.stsCode",
      "include": [
        "00000",
        "99998",
        "99999"
      ]
    },
    "min_doc_count": 0
  }
}

これに対して以下のようなエラーが返ってきています。

"result": {
  "execution_time": "2022-05-30T01:21:03.647Z",
  "execution_duration": 0,
  "input": {
    "type": "search",
    "status": "failure",
    "error": {
      "root_cause": [
        {
          "type": "parsing_exception",
          "reason": "Expected [START_OBJECT] under [min_doc_count], but got a [VALUE_NUMBER] in [sts_success_count]",
          "line": 1,
          "col": 298
        }
      ],

何か記述に誤りがありますでしょうか?
ご教示いただけますと幸いです。

min_doc_countはterms aggregationのパラメーターなので、

"aggs": {
  "sts_success_count": {
    "terms": {
      "field": "info.stsCode",
      "include": [
        "00000",
        "99998",
        "99999"
      ],
      "min_doc_count": 0
    }
  }
}

です。jsonのネスト位置って見づらいですよね…。

1 Like

ありがとうございます。
パラメータの位置を勘違いしてしまっておりました。この通りに位置を修正し、エラーは解消できました。
通知条件自体はうまくいったので、通知設定を行い、以下の通りにメール文面を組み立てようとしました。

"actions": {
    "send_email": {
      "email": {
        "profile": "standard",
        "to": [
          "xxx@xxx.xxx"
        ],
        "cc": [
        ],
        "subject": "title",
        "body": {
          "html": "時間:{{ctx.payload.time_triggered}}<br />直近 1 時間の総取引数:{{ctx.payload.ctx.payload.hits.total}}<br />直近 1 時間の正常取引数:{{ctx.payload.aggregations.sts_success_count.buckets.0.doc_count}} + {{ctx.payload.aggregations.sts_success_count.buckets.1.doc_count}} + {{ctx.payload.aggregations.sts_success_count.buckets.2.doc_count}}<br />"

しかしながらこの記述では、エラーは発生していないのですが、送信された文面に以下の問題がありました。

  1. 「時間」項目が空欄で送信されてしまう
  2. 「直近 1 時間の総取引数」項目が空欄で送信されてしまう
  3. 「直近 1 時間の正常取引数」項目が計算できずそのまま出力されてしまう

これらの問題を解消して想定通りの値をメール文面として送信する方法はありますでしょうか?

よろしくお願いします。

time_triggeredの指定が違う場所を指しているように思います。Watch execution contextを確認してみてください。

なお、前の話に戻りますが、filter aggregationを使うとこのようなクエリになりそうです。この方がすっきりしますね。

GET test_filter_agg/_search
{
  "size":0,
  "aggs": {
    "RESULT": {
      "filters": {
        "filters": {
          "SUCCESS": {"terms": {
            "info.stsCode": [
              "00000",
              "99998",
              "99999"
            ]
          }},
          "ALL":{"match_all":{}}
        }
      }
    }
  }
}
{
  "aggregations" : {
    "RESULT" : {
      "buckets" : {
        "ALL" : {
          "doc_count" : 4
        },
        "SUCCESS" : {
          "doc_count" : 3
        }
      }
    }
  }
}

1 Like

3.については"html"の内容はあくまでstringで、Mustache内だけが変換されるので、当然計算されないですね。Mustache内で加算するようなスクリプトが書けるかは分かりません。。前のリプライに入れたfilters aggregationを用いて前もって計算する方が妥当に思います。

ありがとうございます。
文面としてはstringで出すことになるとのこと、理解しました。
他に出したかったエラーのパターンも含めて以下のように書き換えて、期待通りに動作するようになりました。

"aggs": {
  "RESULT": {
    "filters": {
      "filters": {
        "SUCCESS": {
          "terms": {
            "info.stsCode": [
              "00000",
              "99998",
              "99999"
            ]
          }
        },
        "ERR_1": {
          "terms": {
            "info.stsCode": [
              "00002"
            ]
          }
        },
        "ERR_2": {
          "terms": {
            "info.stsCode": [
              "50120"
            ]
          }
        },
        "ALL":{
          "match_all":{}
        }
      }
    }
  }
}

文面は以下の通りにしました。

 "html": "時間:{{ctx.execution_time}}<br />直近 1 時間の総取引数:{{ctx.payload.aggregations.RESULT.buckets.ALL.doc_count}}<br />直近 1 時間の正常取引数:{{ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count}}<br />直近 1 時間のエラー1件数:{{ctx.payload.aggregations.RESULT.buckets.ERR_1.doc_count}}<br />直近 1 時間のエラー2件数:{{ctx.payload.aggregations.RESULT.buckets.ERR_2.doc_count}}<br />"

もう一点、追加で、「その他エラー」として、上記termsで絞っているstsCode以外のコードが出た場合の件数も出力したいと考えています。

算術演算子が文中に利用できないということなので、filtersの中身にstsCode以外という条件で新規に追加してやる必要があるかと思うのですが、どのような記述方法ができるでしょうか?
SQLでいうところのNOT INのようなパターンの記法が見つからず、ご教授いただきたいです。

自己解決しました。

"OTHER_ERR": {
  "bool": {
    "must_not": {
      "terms": {
        "info.stsCode": [
          "00000",
          "99998",
          "99999",
          "00002",
          "50120"
        ]
      }
    }
  }
},

課題の解決に向け様々なアドバイスを頂戴し、誠にありがとうございました。

1 Like

たびたび申し訳ございません。
どうやら実装に不足があったようなので改めて確認をさせていただけますでしょうか。

本件で作成したwatcherについて実際に稼働させてみたところ、以下のconditionを満たしていないにも関わらずメール通知が飛ぶようになってしまいました。

本来やりたかった内容としては、
SUCCESSの件数 / ALLの件数 = SUCCESSの割合 を求め、
SUCCESSの割合が閾値(threshold)未満となっている場合にアラートを飛ばす、というものです。

"condition": {
  "script": {
    "source": "if ( ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count / ctx.payload.aggregations.RESULT.buckets.ALL.doc_count < params.threshold ) { return true; } return false;",
    "lang": "painless",
    "params": {
      "threshold": 0.85
    }
  }
},

エラーは発生しておらず、resultは以下の通りでした。

"result": {
  "execution_time": "2022-05-30T08:18:11.145Z",
  "execution_duration": 72,
  "input": {
    "type": "search",
    "status": "success",
    "payload": {
      "_shards": {
        "total": 15,
        "failed": 0,
        "successful": 15,
        "skipped": 0
      },
      "hits": {
        "hits": [],
        "total": 4801,
        "max_score": null
      },
      "took": 5,
      "timed_out": false,
      "aggregations": {
        "RESULT": {
          "buckets": {
            "ALL": {
              "doc_count": 4801
            },
            "SUCCESS": {
              "doc_count": 4379
            },
            "ERR_1": {
              "doc_count": 0
            },
            "ERR_2": {
              "doc_count": 177
            },
            "OTHER_ERR": {
              "doc_count": 245
            }
          }
        }
      }
    },

上記のresultによればSUCCESSの割合は
4379 / 4801 = 0.912...
となるので発火しない想定でしたが、通知が飛んでしまっています。
閾値を変更しながら通知を確認してみたのですが、conditionの条件が閾値によらず発火する状態になってしまっているようです。
scriptの内部ではdoc_countの値を正しく扱えていないのでしょうか?
それともif文の書き方に問題があるのでしょうか?

resultの中にconditionなかったですか?
script自体はあっていそうに見えますが…。

resultのconditionは以下の内容しか載っていませんでした。

    "condition": {
      "type": "script",
      "status": "success",
      "met": true
    },

整数型で丸められてしまったのかと思い、両辺それぞれ100倍して計算するように試してみましたが、それでも結果は変わらず、trueが返ってしまっています。
試したのは以下のようなscriptです。

  "condition": {
    "script": {
      "source": "if ( ( ( ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count * 100 ) / ( ctx.payload.aggregations.RESULT.buckets.ALL.doc_count * 100 ) ) * 100 < 85 ) { return true; } return false;",
      "lang": "painless"
    }
  },

これでも85%以上の割合でも通知が飛んでしまっています。

たとえばconditionでどのような計算が行われているかデバッグできればより掘り下げた調査できるかとも思うのですが、何かやり方はございますでしょうか?

改めて確認したところ、以下の2つのconditionがtrueを返していました。

  "condition": {
    "script": {
      "source": "ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count * ctx.payload.aggregations.RESULT.buckets.ALL.doc_count == 0",
      "lang": "painless"
    }
  },
"condition": {
  "script": {
    "source": "ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count / ctx.payload.aggregations.RESULT.buckets.ALL.doc_count == 0",
    "lang": "painless"
  }
},

つまり少なくとも ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count が0を返している、という結果だと思います。
しかしながら、以下の結果を確認するとこちらはそれぞれfalseなので、上記の内容と一致しません。

"condition": {
  "script": {
    "source": "ctx.payload.aggregations.RESULT.buckets.ALL.doc_count == 0",
    "lang": "painless"
  }
},
"condition": {
  "script": {
    "source": "ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count == 0",
    "lang": "painless"
  }
},

算術演算子を用いた場合のみ条件がおかしくなっているように見受けられます。
こちらの回避方法をご存知でしたらご教示いただきたいです。

整数型で丸められたというのはありそうですね。

その場合は、

if ( ( ( ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count * 100 ) / ( ctx.payload.aggregations.RESULT.buckets.ALL.doc_count * 100 ) ) * 100 < 85 )

ではなく、

if ( ( ctx.payload.aggregations.RESULT.buckets.SUCCESS.doc_count * 100 ) / ctx.payload.aggregations.RESULT.buckets.ALL.doc_count  < 85 )

とすべきかと思います。分母分子両方100倍してしまうと結局丸まってしまいますので。

ただ、それでもSUCCESS.doc_count*ALL.doc_count==0trueの部分は説明がつきません。私の手に余る気がするので、解決しない場合は、また新たにスレッドを立てて聞いていただいた方が良いかもしれません。

1 Like

ありがとうございます。
ひとまず当初の目的は果たしておりますので、いったんこのスレッドはクローズとして、新たにスレッドを立ち上げさせていただこうと思います。

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.