こんにちは。DBRE チーム の飯塚です。
サイボウズのプライベートクラウド Neco ではメトリクスの保存とモニタリングに VictoriaMetrics を利用しています。私はプライベートクラウドの利用者として、この VictoriaMetrics にメトリクスを保存したり、メトリクスをまとめて見るためのダッシュボードを作ったり、サービスレベルと関連するメトリクスにアラートを設定したりしています。
VictoriaMetrics でメトリクスを分析・集計するためのクエリを書くのに使われるクエリ言語が MetricsQL です。MetricsQL は、Prometheus のクエリ言語である PromQL に独自の拡張を加えたクエリ言語です。この 独自の拡張部分 の中にはかなり便利な機能もあり、私にとって「この機能なしでは PromQL には戻れないよ 😭」と思うような機能さえあります。
この記事では、私が特に便利だと感じている MetricsQL の拡張部分をいくつか紹介します。
この記事は CYBOZU SUMMER BLOG FES '25 の記事です。
WITH templates (WITH expressions)
WITH templates はクエリの式を書くときに変数のようなものを導入できる機能です。公式の解説は以下のページに記載されています。
例を挙げます。Kubernetes 上の MySQL コンテナの CPU 使用率(.spec.containers[].resources.limits.cpu に対する CPU 時間の割合)を表示するダッシュボードを作るなら以下のようなクエリになるでしょう。
// $namespace や $pod は Grafana から与えられる変数のプレースホルダです
rate(container_cpu_usage_seconds_total{namespace="$namespace",pod="$pod",container="mysqld"}[5m])
/ on (pod)
kube_pod_container_resource_limits{namespace="$namespace",pod="$pod",container="mysqld",resource="cpu"}
これくらいのサイズのクエリであれば、慣れている人ならすぐに usage / limits という単純な割り算の形をしていることを見抜けるでしょう。しかしこんな単純な形のクエリだけでダッシュボードが作られることはほぼありません。少し複雑なクエリを書こうとしただけで、解説なしでクエリを読み解くのが難しくなってしまうことが予想されます。
クエリを読み解くのを難しくしているのは、長いメトリクス名(container_cpu_usage_seconds_total など)や複雑なフィルタ({namespace="$namespace",pod="$pod",container="mysqld"} など)が脳内のワーキングメモリを圧迫しているからです。これらは頭の片隅に追いやっておき、 usage / limits という単純な割り算の形で書ければよいのではないでしょうか? そういう願いをかなえてくれるのが WITH templates です。WITH templates では以下のように変数を設定することができます。
// $namespace や $pod は Grafana から与えられる変数のプレースホルダです
WITH (
usage = rate(container_cpu_usage_seconds_total{namespace="$namespace",pod="$pod",container="mysqld"}[5m]),
limits = kube_pod_container_resource_limits{namespace="$namespace",pod="$pod",container="mysqld",resource="cpu"}
)
usage / on (pod) limits
これで usage / limits という単純な割り算の形式で見通せるようになりました。WITH templates にはそのほかにも次のような機能があります。
- フィルタのラベル部分だけ変数に置くこともできます。
WITH ( commonFilters = {namespace="$namespace",pod="$pod",container="mysqld"} )のように定義しておいてrate(container_cpu_usage_seconds_total{commonFilters})のように使います - 関数を定義することもできます。
WITH ( f(a,b) = a + b )のように定義して使います。
histogram_share
何らかのヒストグラムのパーセンタイルを基準としてアラートを設定するために histogram_quantile を使ったことがある方も多いのではないでしょうか? 例として、何らかの HTTP サーバーのレスポンスタイムの時系列データがあるとします。このレスポンスタイムの遅延の傾向を捉えてアラートにする用途には histogram_quantile が有用でしょう。

- HTTP サーバーのレスポンスタイムの時系列データがあり、直近 3 分間のレスポンスタイムの傾向に着目するとします(左の図)
- 直近 3 分間のレスポンスタイムのヒストグラムを作成します(中央の図)
- ヒストグラムから累積分布を作成します(右の図)
この累積分布から、例えば「95 パーセンタイルが 800 ミリ秒を超えていたら警告を出す」といった用途に histogram_quantile は利用できます。その際のアラートのクエリは以下のようになるでしょう。
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[3m])) > 0.8
ここで例として設定した「95 パーセンタイルが 800 ミリ秒を超えていたら警告を出す」というアラートは、言い換えると「800 ミリ秒以内のレスポンスの割合が 95%を下回ったら警告を出す」ということです。サービスレベルについて議論する際には、前者のように パーセンタイルに対して許容されるレスポンスタイム を考えるよりも、むしろ後者のように、あるレスポンスタイムに対して許容されるエラーバジェット を議論することのほうが多いのではないでしょうか?そう考えると、アラートの状況をダッシュボードに表示したいときには histogram_quantile の 逆関数 のようなものが欲しくなることもあるでしょう。
このために MetricsQL で提供されている関数が histogram_share です。
今回の例では histogram_quantile と histogram_share はそれぞれ以下のような意味を持つことになります。
- histogram_quantile: 「直近 3 分間のレスポンスタイムの 95 パーセンタイルの値」
- histogram_share: 「直近 3 分間のレスポンスタイムのうち、800 ミリ秒以内のものの割合」
histogram_quantile の代わりに histogram_share でアラートを設定するのであれば、以下のようなクエリになるでしょう。
histogram_share(0.8, rate(http_request_duration_seconds_bucket[3m])) < 0.95
range_min, range_max
私の所属する DBRE チームは社内に向けて MySQL マネジメントサービスを提供する立場として、プライベートクラウド上に作成された全ての MySQL の状況を概観できるダッシュボードを作成しています。よく見るパネルは moco_cluster_healthy というメトリクスに関するもので、3 台で冗長化された MySQL クラスタのうち 1 台でも異常があれば 0、すべて正常なら 1 になるメトリクスです。我々のプライベートクラウドでは数百以上の MySQL インスタンスが稼働しているためサーバー故障などの要因で Unhealthy になること自体はよくあり、短時間だけ Unhealthy になること自体にはアラートを設定していません(また、典型的な故障パターンでは自動復旧するようになっています)。しかし意図しない理由によって Unhealthy になっていないかという懸念はあるため、週次でこのメトリクスの状況をざっと確認しています。確認にはダッシュボード内の以下のようなパネルを参照しています。

前置きが長くなりましたが、ここで確認している moco_cluster_healthy は数百インスタンス分のメトリクスがあるため、Grafana でそのまま全メトリクスを表示すると重くなったり操作が難しかったりします。こういう場面で重宝しているのが MetricsQL の range_min, range_max です。

MetricsQL の range_min, range_max は PromQL の min_over_time, max_over_time などとは別の挙動をします。PromQL の min_over_time, max_over_time はそれぞれのデータポイントで [d] のようにして指定した lookbehind window の中での最小値や最大値を返す関数です(画像内の中央の図)。Grafana で指定した時間の範囲は結果に関係しません。
一方、MetricsQL の range_min, range_max は 渡された時系列データの全編を通した最小値や最大値 を返します。Grafana と一緒に使った場合には、Grafana で指定した時間内での最小値や最大値に置き換わります(画像内の下側の図)。これをどのように活用できるかというと、Grafana で指定した時間の範囲の中で一度でも moco_cluster_healthy が 0 になったことがあるメトリクスだけ取り出す、といったことができるようになります。
moco_cluster_healthy{namespace="$namespace"} and (range_min(moco_cluster_healthy{namespace="$namespace"}) == 0)
ここでは logical binary operator の and を使って moco_cluster_healthy{namespace="$namespace"} から「Grafana で指定した時間の範囲の中で一度でも moco_cluster_healthy が 0 になったことがあるメトリクス」だけ取り出しています。
topk_max, topk_min, bottomk_max, bottomk_min
topk_max, topk_min, bottomk_max, bottomk_min も利用目的としては range_min, range_max と似ています。記事の冒頭で示した CPU 使用率に関するクエリですが、数百インスタンス分の CPU 使用率をすべて描画してもぐちゃぐちゃした訳のわからないグラフになりがちです。本当に関心があるのは「CPU 使用率が高かったことがある MySQL の前後の CPU 使用率の推移」だったり、「CPU 使用率がずっと低い MySQL の状況」だったりします。こういうときに使えるのが topk_max, topk_min, bottomk_max, bottomk_min などの関数群です。
topk_max を例にして解説します。topk_max はまず指定された時間の範囲(Grafana で時間範囲を入力するボックスで指定した時間範囲が使われる)の中での全編に渡っての最大値をそれぞれのメトリクスについて計算します。そしてそのメトリクスごとの最大値を使って、多数のメトリクスから top k だけを抽出します。これは「CPU 使用率が高かったことがある MySQL の前後の CPU 使用率の推移」を表示するのに適しています。

Prometheus の topk 関数との差に注意してください。topk 関数は指定された時間範囲全編に渡った演算ではなく、それぞれのデータポイントで topk を計算するだけです。
おわりに
この記事では私が運用のためのダッシュボードを作成する際に便利に活用している MetricsQL の便利な機能について解説しました。運用に携わる皆様の一助となれば幸いです。
私たちはサイボウズのプロダクトが利用するデータストアの信頼性を高めるために日々活動しています。もしご興味を持っていただけたら、ぜひ採用ページをご覧ください。