mcpField
S
stream-aggregation-helper

stream-aggregation-helper

github.com/victoriametricsクラウド・DevOps59174 回インストール2 週間前に更新
リポジトリ

stream-aggregation-helper について

VictoriaMetricsの運用担当者向けに、トポロジーの評価、ルール選定、YAML設定と段階的な導入計画の作成を支援するストリーム集約スキルです。検証用クエリやメトリクスを示し、シャードキーなどの正確性条件も確認します。

主な機能

  1. 01集約の適合性を評価
  2. 02カーディナリティと構成を調査
  3. 03間隔・関数・ラベルを選定
  4. 04YAMLと導入計画を作成
  5. 05クエリとメトリクスで検証

活用シーン

  1. 01保存前に時系列のカーディナリティを削減
  2. 02vmagent向けの集約設定を設計
  3. 03シャーディングやHAの集約結果を確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/victoriametrics/skills --skill stream-aggregation-helper

インストール後、Claude Code で/stream-aggregation-helperを入力して読み込まれたか確認します。

詳細

ストリーム集約ヘルパー

VictoriaMetrics のストリーム集約ルールを設計、デプロイ、検証します。ストリーム集約では、サンプルがストレージに到達する前に、vmagent(または単一ノードの vmsingle)でサンプルを時間とラベルで集約することにより、カーディナリティ、サンプル頻度、またはクエリコストを削減します。

このスキルでは、次の 5 つのフェーズを進めます。

  1. ゲート — この問題にストリーム集約は適切なツールですか?
  2. 情報収集 — 現在のカーディナリティ、収集トポロジー、利用者が必要としている情報。
  3. 判断 — パイプライン内の配置場所、間隔、出力関数、by/without、スケーリング。
  4. 設定 — すぐに適用できる YAML と、段階的なロールアウト計画を作成します。
  5. 検証 — ルールが意図どおりに動作していることを確認するクエリと vm_streamaggr_* メトリクス。

環境

victoriametrics-query スキルと同じ環境変数を使用します。このスキルは環境変数がなくても(ヒアリングのみのモードで)動作しますが、ユーザーの VictoriaMetrics に直接クエリできる場合は、より正確な結果が得られます。

# $VM_METRICS_URL - base URL (optional)
#   cluster: export VM_METRICS_URL="https://vmselect.example.com/select/0/prometheus"
#   single:  export VM_METRICS_URL="http://localhost:8428"
# $VM_CURL_CONFIG - curl config file with auth header (unset if no auth)
#   Remote: export VM_CURL_CONFIG="$HOME/.config/victoriametrics/curl.conf"
#   Local:  leave unset (defaults to /dev/null - no auth)

フェーズ 1:ストリーム集約は適切なツールですか?

以下のすべてに当てはまる場合、ストリーム集約が適切です。

  • ルールが生成するすべての出力系列について、その系列に寄与するすべての入力サンプルが、単一の集約プロセスに到達します。これは、一般的な次の3つのトポロジーで成り立ちます。
    1. 1つの vmagent(または vmsingle)が、対象系列のすべてのサンプルを受け取ります。
    2. 複数の vmagents がラベル(例:instance、kubernetes_namespace、__name__)によってトラフィックをシャーディングし、集約でシャードキーを保持します。Phase 3a(ケース F)のシャードキー不変条件を参照してください。tier-1 の vmagents が instance でシャーディング → tier-2 の vmagents が instance 内で集約するような階層型トポロジーも、明示的にサポートされています。
    3. HA レプリカがすべてのサンプルを受け取り、かつ集約の出力が重複排除に対応しています(ゲージ型:min、max、avg、rate_*、increase*、last、quantiles など)。カウンター型の出力(total、total_prometheus、histogram_bucket)は、各レプリカが独立した単調増加状態を保持するため、HA 集約できません。「高可用性」セクションを参照してください。
  • ユーザーが、対象メトリックのソースごとの解像度(ポッドごと、インスタンスごと)を失ってもよいと考えている、または実際には利用していない高カーディナリティラベルだけを削除したいと考えています。
  • コンシューマー(ダッシュボード、アラート、記録ルール)が、削除されるディメンションを必要としていないか、集約後のメトリック名に移行できます。
  • vmagent の再起動時に状態が失われても問題ありません。集約状態はメモリ内に保存されます。再起動時には、デフォルトで最初の1~2インターバルが破棄されます。

シャードキー不変条件:入力サンプルがすべて同じ集約プロセスに送られる場合にのみ、出力系列は正しい結果になります。ルールに置き換えると、ストリームのシャーディングに使用するラベルは、下流のすべての集約ルールで by: に含める(または without: に含めない)必要があります。それらを削除すると、各シャードで部分集約が生成され、気付かないまま誤った結果になります。

これらの条件を満たさない場合は、ユーザーに別の方法を案内してください。

状況より適したツール
使用していないメトリック全体を削除したいmetric_relabel_configs の action: drop — victoriametrics-unused-metrics-analysis を参照
時間バケット化せずにラベルを削除したいmetric_relabel_configs の action: labeldrop、または -streamAggr.dropInputLabels
既存データを過去にさかのぼってダウンサンプリングしたいvmstorage のダウンサンプリング(-downsampling.period)
取り込みではなく、クエリ用の派生メトリックが必要vmalert の記録ルール
単一ノード構成で、ストレージの前にストリーム集約したいvmsingle の -streamAggr.config で問題ありません。同じエンジンです。その旨を伝えてください
ストリームを分割するロードバランサーの背後にある(ラウンドロビン)ラベルによってトラフィックを決定論的にシャーディング(-remoteWrite.shardByURL.labels)し、シャードキーを保持しながら集約します。ラウンドロビンのロードバランサーは、1つの出力系列をレプリカ間で分割するため、結果が不完全になります。よくある間違い › ロードバランサーの背後 を参照してください
階層型 vmagents(tier-1 がシャーディングし、tier-2 が集約)サポートされており、大規模環境で一般的です。tier-2 の集約ルールでは、by: にシャードキーを含める(または without: から省く)必要があります。以下のケース F を参照してください。

条件を満たす場合は続行します。満たさない場合は、代替手段を推奨して終了してください。


フェーズ 2:情報収集

事実を3つのグループに分けて収集します。VM_METRICS_URL が設定されている場合に限り、ライブクエリを並行して実行してください。それ以外の場合は、同じ質問をユーザーに直接してください。

2a. カーディナリティの把握

問題となっているメトリクスまたはメトリクスセットは何ですか? どのラベルによって、シリーズ数がどれくらいになっていますか?

# Top series by metric name (find the offenders)
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
  "$VM_METRICS_URL/api/v1/status/tsdb?topN=20" | \
  jq '.data | {totalSeries, seriesCountByMetricName, seriesCountByLabelName}'

# For each suspect metric, see unique values per label
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
  "$VM_METRICS_URL/api/v1/status/tsdb?topN=20&match[]=METRIC_NAME" | \
  jq '.data.labelValueCountByLabelName'

集約後のカーディナリティを直接測定します。 by: または without: の候補が決まったら、ラベルごとの一意な値の数を掛け合わせるのではなく、現在のデータに対して同等のグルーピングを実行します。ラベルの積の式では、ラベル値のあらゆる組み合わせが存在すると仮定しますが、実際にはそうでないことがほとんどです。さらに、ラベル間に相関がある場合や、列挙し忘れたラベルがある場合には、過小評価になるおそれがあります。測定値を正として扱ってください。

# Post-aggregation series count, using the labels you plan to KEEP.
# Replace <metric> and <kept_labels>. For counters use rate(...[5m]); for gauges use last_over_time.
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
  --data-urlencode 'query=count(sum(rate(<metric>[5m])) by (<kept_labels>))' \
  "$VM_METRICS_URL/api/v1/query" | jq '.data.result'

# Equivalent form using the labels you plan to DROP:
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
  --data-urlencode 'query=count(sum(rate(<metric>[5m])) without (<dropped_labels>))' \
  "$VM_METRICS_URL/api/v1/query" | jq '.data.result'

これにより、ルールが現在のデータ上で生成する出力シリーズの実数がわかります。これはフェーズ 4d の削減効果を見積もる際の適切な基準です。設定を確定する前に、候補となる各ルールについて実行してください。

ライブクエリを利用できない場合のヒアリング項目:

  • 問題となっているメトリクスまたはメトリクスのパターンは何ですか?
  • そのメトリクスのシリーズ総数はおよそどれくらいですか?
  • どのラベルがあり、それぞれの一意な値はおよそいくつありますか?
  • カウンター、ゲージ、ヒストグラムのどれですか?
  • スクレイプ間隔/プッシュ頻度はどれくらいですか?

ライブクエリを実行できない場合、ラベルの積の式は上限値としてのみ扱い、データにアクセスできるようになったら実際の集約後の数を検証する必要があることを、ユーザーに明確に伝えてください。

2b. 収集トポロジー

現在、サンプルはどのように流れていますか? 次のいずれかを図示してください。

A: app  -> vmagent -> remote write -> TimeSeries DB         (vmagent already in path)
B: app  -> remote write -> TimeSeries DB                    (no vmagent — must insert one)
C: app  -> [Prometheus | OTel | StatsD-exporter] -> remote write -> TimeSeries DB
D: app  -> load balancer -> N vmagents -> TimeSeries DB     (LB splits stream — dangerous for SA)
E: app  -> vmagent (HA pair / N replicas) -> TimeSeries DB  (multi-vmagent — dedup needed)
F: app  -> vmagent cluster (sharded) -> TimeSeries DB       (sharded by `-remoteWrite.shardByURL`)

宛先は汎用的な「TimeSeries DB」として示しています。ストリーム集約は vmagent 側の機能であり、リモートライト互換のバックエンド(vmstorage/vmsingle、Prometheus、Cortex、Mimir、Thanos receive など)で利用できます。以下の注意事項のうち、vmstorage の重複排除(-dedup.minScrapeInterval)に関するものは、バックエンドが VictoriaMetrics の場合にのみ該当します。他のバックエンドでは、バックエンド側で重複排除を行うか、HA 集約によって重複データが書き込まれることを許容してください。

ヒアリング項目:

  • すでに vmagent が経路にありますか? ある場合、レプリカはいくつですか?
  • サンプルはアグリゲーターに届く前にロードバランサーを経由しますか?
  • 同じシリーズは厳密に 1 つのソースインスタンスから送られていますか? それとも複数のソースインスタンスから送られていますか(例:HA ペア、マルチリージョン)?
  • K8s のユーザーの場合:VMAgent CRD(vm-operator)ですか、それとも raw deployment ですか?
2c. 利用者の意図

下流のクエリでは、実際に何が必要ですか?

  • ダッシュボード/アラートでは、どの解像度でクエリしていますか?(例:rate(...[5m])、5 分間の p99)
  • このメトリクスについて、ポッド単位/インスタンス単位の解像度を必要とする利用者はいますか?「いいえ、サービス全体のものだけ」という場合は、SA が適していることを最も強く示すサインです。
  • アラートルールやレコーディングルールで、メトリクス名が参照されていますか? SA の後にメトリクス名を変更すると、それらが動作しなくなります。更新が必要なクエリを記録してください。
  • 異なる集約を必要とする別々の宛先はありますか? ある場合はリモートライト単位の設定(-remoteWrite.streamAggr.config)を使用してください。そうでない場合は、グローバル設定(-streamAggr.config)を優先してください。

フェーズ 3:判断

各メトリクスまたはグループについて、5 つの項目を決定します。

3a. vmagent のデプロイ先
トポロジSA への適合性備考
A. vmagent がすでに経路上にあり、レプリカが 1 つ最適です。集約は正確です。1 つの vmagent がすべてのサンプルを確認します。
B. vmagent がまだないストレージへのホップの直前に vmagent を挿入します。プロセスが 1 つ増えますが、集約、バッファリング、再試行を追加コストなしで利用できます。
C. Prometheus/OTel コレクターの後ろコレクターとリモート書き込みの間に vmagent を挿入するか、スクレイピングに問題がなければコレクターを vmagent に置き換えます。ストリーミング集約をサポートしていない限り、コレクター内で集約しないでください。
D. ロードバランサーの後ろ**集約しないでください。**各 vmagent がサンプルの一部しか確認できないため、集約が不完全になります。先にトラフィックを __name__ またはラベルでシャーディングする(-remoteWrite.shardByURL.labels)か、単一の集約器で終端します。
E. 複数の vmagent レプリカ(HA)重複排除に対応した出力(ゲージ形式:min、max、avg、rate_*、increase*、last、quantiles、sum_samples など)のみに適しています。すべてのレプリカに同一の入力を送り、同一の設定で実行し、同一の出力ラベルを生成する必要があります。vmstorage の重複排除(-dedup.minScrapeInterval)によって重複がまとめられます。レプリカごとに異なるタグを付けないでください。ストレージ上で、重複排除に対応した時系列が恒久的に二重になってしまいます。カウンター形式の出力(total、total_prometheus、histogram_bucket)では、再起動後に各レプリカの単調増加状態が一致しなくなるため、この方法での HA はできません。高可用性のセクションを参照してください。
F. シャーディングされた vmagent フリート(単一層、または多層トポロジの第 1 層)集約でシャードキーが保持される場合に適しています。シャードラベル(例:instance、kubernetes_namespace、__name__)は、下流のすべてのルールの by: リストに含めるか、同等の方法として without: に含めない必要があります。シャードキーを削除すると、各シャードがそれぞれ集計結果の一部を生成し、合計が気付かれないまま誤った値になります。一般的な設定例:instance でシャーディングし、by: [instance, method, status_code, le] で集約する。kubernetes_namespace でシャーディングし、by: [kubernetes_namespace, workload] で集約する。シャードキーによって 1 つの時系列が複数のシャードに分割される場合(例:Pod をまたいで集約したいのに pod でシャーディングする場合)、このトポロジはシャードキーの不変条件を満たせません。シャーディングをやり直すか、シャーディングの前に第 1 層の vmagents で集約してください。**ヒストグラムを集約する場合、le(または vmrange)でシャーディングしないでください。**1 つのヒストグラムのバケットは、すべて同じ集約器に届く必要があります。le でシャーディングすると、同じヒストグラムの le="0.1"、le="0.5"、le="+Inf" などが異なるシャードに送られます。各シャードがバケットの一部しか受信できず、histogram_quantile() の整合性が失われ、分位点が気付かれないままずれていきます。ヒストグラムは、instance、kubernetes_namespace、__name__ など、バケット以外のラベルでシャーディングしてください。
3b. 集約間隔

interval は、次の 2 つの制約で決まります。

  1. 下限:入力のスクレイプ/プッシュ間隔の少なくとも 2 倍にします。それより短いと、サンプルが 1 つだけ届いた場合にバケットが空になるおそれがあります。デフォルトは 2× scrape_interval です(スクレイプ間隔が 15 秒なら 30 秒)。
  2. 上限:コンシューマーのクエリ範囲のうち最短のものを超えないようにします。アラートで rate(...[2m]) を使う場合、間隔は 1 分以下、理想的には 30 秒にする必要があります。5 分ウィンドウのダッシュボードでは、1 分で問題ありません。

その他の調整項目:

  • 遅延が大きい/サンプルの時刻にばらつきがある → 間隔を長くするか、enable_windows: true を有効にします(メモリ使用量は 2 倍になりますが、精度を高めるために 2 ウィンドウ状態を使用します)。ヒストグラムでは特に重要です。
  • レコーディングルールを置き換える → そのルールの評価間隔に合わせます(一般的には 1 分です)。
  • ストレージコスト削減のためにダウンサンプリングする → 一般的には 1~5 分です。5 分を超えると、インタラクティブなクエリの利便性が損なわれます。

デフォルト:高頻度のアプリケーションメトリクスには interval: 30s、レコーディングルールの置き換えには interval: 1m、ダウンサンプリングのみの場合は interval: 5m を指定します。

3c. 出力関数

最も間違えやすい選択肢です。まずメトリクスの種類で選び、次にコンシューマーが必要とするものに合わせてください。

入力の種類コンシューマーが必要とするもの出力備考
Counter出力系列ごとのレートの合計(例:sum(rate(req_total[5m])))rate_sum結果は秒あたりのレートを示すゲージです。sum/avg_over_time で直接クエリしてください。再度 rate() を適用しないでください。
Counter後で rate() を適用するための、区間ごとの増加量(カウンターとして)total出力は、ソースごとのリセットや系列の入れ替わりに強い単調増加カウンターです。rate() でクエリしてください。系列ごとの最初のサンプルを除外するには total_prometheus を使用します。
Counter値としての区間ごとの増加量(カウンターではない)increase / increase_prometheus出力は、区間ごとの差分を示すゲージです。
Counter入力系列全体の平均レートrate_avgレートの合計ではなく、レートの平均を求める場合に使用します。
Histogram bucket(*_bucket、le 形式)histogram_quantile()rate_sum + enable_windows: true推奨パターンです。集約する系列間でバケットのセットが一致している必要があります。
Histogram bucketバケットカウントの合計をカウンターとして取得total使用できますが、rate_sum よりリセットの影響を受けやすくなります。
Gauge最新値last最も低コストです。last_over_time と同じ動作です。
Gaugeインスタンス間の合計sum_samples例:ポッド全体のメモリ量。
Gauge最小値/最大値/平均値min / max / avg関数と同じ名前です。
Gauge分布/パーセンタイルquantiles(0.5, 0.9, 0.99) または histogram_bucketquantiles は、quantile="<phi>" ラベルが付いた phi ごとに出力系列を1つ生成します。histogram_bucket は、後から分位点をクエリできる VictoriaMetrics 形式のバケットを返します。後から任意の分位点が必要な場合は histogram_bucket、セットが固定されている場合は quantiles(...) を選んでください。
Gauge一意な値の数unique_samples特殊な用途向けです。カーディナリティ形式の測定に使用します。
Eitherサンプル数count_samples / count_seriescount_samples は取り込まれたポイント数、count_series は区間内に存在した異なる系列の数です。

最もよくある間違いは、ゲージに total を使うこと、またはカウンターに sum_samples を使うことです。どちらも意味のない結果になります。Counter → total/rate_*/increase*。Gauge → その他すべて。

3d. by と without
  • 削除するラベル(例:pod、instance)がわかっている場合は、without: を使用してください。新しいラベルが後から追加されてもエラーが起きにくいため、このスキルでは without を優先してください。
  • 保持するラベルの正確なセットがわかっていて、新しいラベルを確実に削除したい場合は、by: を使用してください。制御されていないラベルが多数ある入力では必須です。
  • ヒストグラムバケットを集約する場合は、必ず le を保持してください。
  • 下流のクエリですでにグループ化に使っているラベルは、必ず保持してください。
  • 明らかにカーディナリティが高く、使用されないラベルを削除してください:pod、instance、replica、container_id。シャード化されたトポロジー(ケース F)では、シャードキーを保持してください。HA トポロジー(ケース E)では、レプリカ間でラベルのセットを同一にし、レプリカごとのタグを付けないでください。
  • drop_input_labels と without/by の違い:どちらも出力からラベルを削除します。ラベルが無制限(例:request_id、trace_id、session_id)の場合は、drop_input_labels(または -streamAggr.dropInputLabels フラグ)を優先してください。重複排除と状態キーの設定より前にラベルを取り除くため、一時的にも値ごとの状態が集計器に割り当てられることはありません。カーディナリティが妥当な範囲で、出力には含めたくないラベルには without/by を使用してください。
3e. vmagent をスケールするべきですか?

単一の vmagent で、控えめなハードウェアでも毎秒数百万サンプルを処理できます。次の場合にのみスケールしてください。

  • 集約側の vmagent の CPU 使用率が、現在の負荷で 70% を超えた状態が続く。
  • histogram_quantile(0.99, sum(rate(vm_streamaggr_flush_duration_seconds_bucket[1m])) by (vmrange)) が集約間隔に近づいている。
  • アクティブな出力系列が多く、メモリに負荷がかかっている(出力系列ごとに RAM を一定量消費し、enable_windows: true の場合はその量が 2 倍になります)。
  • 入力レートが多数のソースに分散しており、1 つの vmagent に集約するのが現実的でない。

スケールする場合:

  • 系列でシャーディング(-remoteWrite.shardByURL.labels)して、各入力系列が必ず 1 つの vmagent に届くようにします。各シャードは出力の異なるスライス(シャードキーのラベル値が異なる)を生成します。シャード間のロールアップはクエリ時に行われます。フェーズ 3a(ケース F)を参照してください。
  • HA レプリカ(すべてのレプリカがすべてのサンプルを受信):同一の設定と同一の出力ラベルを使用します。vmstorage の重複排除によって重複がまとめられます。重複排除に対応した出力でのみ有効です。「高可用性」セクションを参照してください。
3f. 集約出力の利用方法

関連する 2 つの判断があります。下流に集約とともに何を流すか、そして集約メトリックの名前をどうするかです。

入力の保持 — ルールに一致した後、生の(未集約の)サンプルをどうするかです。これらは YAML のフィールドではなく、コマンドラインフラグ(またはリモート書き込みごとの同等の設定)です。

ユーザーの意図フラグ設定結果
集約のみ — 一致した生データを破棄 (通常運用時のデフォルト)どちらのフラグも指定しない集約データが書き込まれます。一致した生データは破棄されます。ルールに一致しないサンプルは引き続き書き込まれます。最も一般的な最終状態です。
生データと集約データの両方 (ロールアウトのステージ 2、またはコンシューマーが引き続き完全な解像度を必要とする移行期間)-streamAggr.keepInput=true(または -remoteWrite.streamAggr.keepInput=true)集約データとすべての生データが書き込まれます。カーディナリティの削減はまだ実現されません。一時的に使用してください。
集約のみ — ルールに一致しないデータもすべて破棄 (専用アーカイブ/長期保存用の経路)-streamAggr.dropInput=true(または -remoteWrite.streamAggr.dropInput=true)集約データが書き込まれます。一致した生データも一致しなかった生データも、どちらも破棄されます。宛先に集約データ以外は何も含めない場合にのみ使用してください。たとえば、長期保存先を対象とするリモート書き込みごとの SA 設定で、別の(集約しない)リモート書き込みにより完全な解像度のデータを別の場所へ送信する場合です。

これらのフラグは、4c のロールアウトフェーズと組み合わせて使用します。通常は検証中に keepInput=true で開始し、その後これを削除してデフォルトの通常運用状態にします。宛先が純粋な集約データの格納先である場合にのみ、dropInput=true を設定してください。

出力メトリックの命名 — デフォルトでは、集約メトリック名は自動生成され、内容が分かる名前になります。

<original_name>:<interval>[_by_<labels>][_without_<labels>]_<output>

ラベルリストはアルファベット順に並び、出力が quantiles(...) の場合は、出力系列ごとに quantile="<phi>" ラベルが付きます。デフォルトのままにすることをおすすめします。クエリ時に集約データを生のメトリックと区別でき、生データが引き続き流れている場合の衝突も防げます。

具体的な理由がある場合にのみ上書きしてください。

  • keep_metric_names: true(ルールごと) — 集約系列に元のメトリック名を保持します。outputs: にエントリが 1 つだけの場合に限り有効です。デフォルトの動作(一致した生データはリモート書き込みから自動的に破棄されます)では安全です。-streamAggr.keepInput=true を設定している場合はリスクがあります。その場合、集約データと一致した生データが異なる意味のまま同じ名前を共有するため、keep_metric_names を有効にする前にデフォルトに戻してください。

  • output_relabel_configs:(ルールごと) — 書き込み前に集約サンプルを全面的に再ラベル付けします。サフィックスの削除、プロジェクトの命名規則の適用、ラベルの書き換えに使用します。

    output_relabel_configs:
      # Strip the auto-added ":interval_..._output" suffix
      - source_labels: [__name__]
        regex: '(.+):.+'
        target_label: __name__
        replacement: '$1'
    
      # Or enforce an "agg_" prefix
      - source_labels: [__name__]
        regex: '(.+)'
        target_label: __name__
        replacement: 'agg_$1'
    

名前を変更する場合は、それに対応するコンシューマーの移行(ダッシュボード、アラート、記録ルール)を計画してください。フェーズ 4c のステージ 4 を参照してください。

3g. 解像度と SA 間隔 — sum(rate(X[5m])) の置き換え

よくある要望は、「ダッシュボードやアラートで sum(rate(req_total[5m])) を使っています。事前集計してください」というものです。素朴な対応では、SA 間隔をレートウィンドウに合わせます。

- match: 'req_total'
  interval: 5m            # too coarse — see below
  by: [<labels>]
  outputs: [rate_sum]

これにより、5 分ごとに 1 つのデータポイントが生成されます。この解像度では、アラート(30 秒〜1 分ごとに評価され、直近のサンプルが必要です)やグラフ(5 分間隔では階段状に見えます)には粗すぎることがよくあります。また、元の rate(...[5m]) はスクレイプのたびに評価されるローリング5 分ウィンドウを提供していましたが、5 分の SA 間隔では5 分間のタンブリングバケットが生成されます。これらは同じものではありません。

推奨されるデフォルト:より細かい SA 間隔(通常は 1m)を使用し、クエリ時に avg_over_time でローリング平滑化を再構成します。

- match: 'req_total'
  interval: 1m
  by: [<labels>]
  outputs: [rate_sum]

クエリの書き換え:

# Original
sum(rate(req_total[5m]))

# After SA with interval: 1m, rate_sum  (rate_sum is already per-second, so don't rate() again)
sum(avg_over_time(req_total:1m_..._rate_sum[5m]))

# Original with grouping
sum by (route) (rate(req_total[5m]))

# After SA — use a subquery to apply avg_over_time after the sum-by
avg_over_time(
  (sum(req_total:1m_..._rate_sum) by (route))[5m:1m]
)

[5m:1m] サブクエリは、「5 分間のウィンドウにわたって、1 分間隔で内部式を評価する」という意味です。つまり、元の rate(...[5m]) が生成していた「1 分ごとに評価されるローリング5 分ウィンドウ」の動作とまったく同じです。

トレードオフの表:

SA の interval5 分間に書き込まれるサンプル数適した用途
5m1バックフィル/長期アーカイブのみ。5 分より短い間隔でクエリするコンシューマーがなく、階段状のグラフでも問題ない場合に適しています。
1m ([5m] を使うコンシューマー向けのデフォルト)5高解像度のグラフやアラートに適しています。クエリ時に avg_over_time(...[5m]) を使うと、元の平滑化を再現できます。
30s10コンシューマーがより短いウィンドウ([2m]、[1m])を使用する場合や、1 分未満のグラフにズームインする場合に適しています。過剰なこともよくあります。

同じ原則は、increase、increase_prometheus、rate_avg、ゲージ出力(min、max、avg、sum_samples)にも当てはまります。SA 間隔は、コンシューマーが使用する最も長い範囲ではなく、コンシューマーがクエリする最短の間隔に基づいて選んでください。クエリ時に、対応する *_over_time ラッパーを使って範囲ウィンドウを再構成します。


フェーズ 4:設定と展開

ユーザーがドキュメントを開かずに上から順に読み、そのまま適用できる、単一の自己完結型プランを作成します。期待される出力形式は次のとおりです。実際のルールに合わせて調整し、該当しないセクションは省略してください。

ストリーム集約プラン:http_duration_p99

判断項目選択内容理由
配置先既存の tier-2 vmagentすでに経路上にあります。tier-1 は kubernetes_namespace でシャーディングし、tier-2 はその範囲内で集約します
間隔1mスクレイプ間隔(15s)の 2 倍です。利用側は [5m] ウィンドウを使用するため、クエリ時にローリング平均を再構成します(フェーズ 3g)
出力rate_sum + enable_windows: trueヒストグラムに推奨されるパターンです。利用側は p99 を必要とします
グルーピングwithout: [pod, instance, path]ソースごとのラベルと生の URL を削除し、kubernetes_namespace(シャードキー)、method、status_code、route、le は保持します
入力の保持デフォルト(切り替え後、該当する生データはリモートライトから削除)切り替え後に生データを必要とする利用側はいません
時系列数(前 → 後)約 5.3M → 約 8.8K(count(sum by (...)(rate(...[5m]))) で測定)約 600 倍の削減
HA/シャーディングTier-2 に HA レプリカが 2 つあります。設定とラベルは同一にし、ストレージで重複排除します出力は rate_sum(HA セーフ)です。レプリカのタグ付けはしないでください。
ストリーム集約設定 — -streamAggr.config(または VMAgent.spec.streamAggrConfig.rules)に貼り付けます
- name: http_duration_p99
  match: 'http_request_duration_seconds_bucket'
  interval: 1m
  enable_windows: true
  without: [pod, instance, path]
  outputs: [rate_sum]
展開手順
  1. -streamAggr.keepInput=true(または CRD で streamAggrConfig.keepInput: true)を指定して設定をデプロイします。リロードします(kill -SIGHUP … または /-/reload)。
  2. 1 時間以上待ってから、クロスチェック用クエリ(下の検証ブロック)を実行します。集約後の p99 は、生の p99 と数パーセント以内で一致するはずです。
  3. -streamAggr.keepInput=true を削除します。リロードします。デフォルトでは、該当する生データがリモートライトから削除されるようになり、カーディナリティが削減されます。
  4. ダッシュボード/アラートを http_request_duration_seconds_bucket:1m_without_instance_path_pod_rate_sum に移行します。サフィックスラベルはアルファベット順に並びます。
検証クエリ
# クロスチェック:生データと集約後の p99(keepInput を有効にしたステージ 2 で実行)
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

histogram_quantile(0.99,
  sum by (le) (
    avg_over_time(http_request_duration_seconds_bucket:1m_without_instance_path_pod_rate_sum[5m])
  )
)

# vmagent の /metrics を監視
# - histogram_quantile(0.99, sum(rate(vm_streamaggr_flush_duration_seconds_bucket{name="http_duration_p99"}[1m])) by (vmrange)) << 1m
# - vm_streamaggr_counter_resets_total — 変動なし(急増は HA の衝突を示します)
# - count({__name__="http_request_duration_seconds_bucket:1m_without_instance_path_pod_rate_sum"}) ≈ 8800

実際のシナリオに合わせて構成を調整してください。たとえば、quantiles(0.95) を使用するゲージでは enable_windows は不要です。記録ルールを置き換えるダウンサンプリングルールでは、比較対象となる生データがないため、クロスチェックを省略できます。デプロイが単一レプリカの場合は、HA の行を削除してください。

4a. 設定を生成する

テンプレート:

- name: <descriptive_name>             # shows up in vm_streamaggr_* metrics
  match: '<series_selector>'           # e.g. '{__name__=~"http_request_.*"}'
  interval: <30s|1m|5m>
  # enable_windows: true               # REQUIRED for histograms; recommended when samples are delayed
  # staleness_interval: 2m             # default 2*interval; raise it if input is sporadic
  # ignore_first_sample_interval: 2m   # for total/increase/histogram_bucket after restart
  # ignore_old_samples: true           # if the stream contains backfilled / replayed data, is automatically applied when enable_windows are enabled
  without: [pod, instance, replica]    # or: by: [route, method, status_code, le]
  outputs: [rate_sum]                  # see output picker above
  # keep_metric_names: true            # only with a single output; drops the ":interval_..." suffix
  # drop_input_labels: [container_id]  # drops labels *before* dedup + aggregation
  # input_relabel_configs: [...]       # rare; transform labels before aggregating
  # output_relabel_configs: [...]      # rare; e.g. strip the auto-added suffix

YAML 内に keep_input や drop_input を記述しないでください。これらはコマンドラインフラグ(-streamAggr.keepInput、-streamAggr.dropInput と、リモートライトごとのバリアント)です。よくあるガイドの多くがここを間違えています。

name フィールドは元のメトリクス名と集約出力を反映するように選びます。ただし、分かりやすいようにシンプルにしてください。

4b. 設定ファイルの場所
方式使用する場面
-streamAggr.configすべての -remoteWrite.url の送信先に複製する、単一のグローバル設定を使う場合。デフォルトの選択肢です。 集約は1回実行されます。
-remoteWrite.streamAggr.config(URLごと)送信先ごとに異なる集約が必要な場合(例:長期保存先には集約済みデータのみ送る)。送信先ごとに集約が実行されるため、CPU 使用量が増えます。
vmsingle -streamAggr.config単一ノード構成の場合。同じエンジンで、同じ YAML を使います。
4c. 段階的なロールアウト
  1. ステージ 1 — 観察。入力を保持したままデプロイし、生データと集約データの両方がストレージに保存されるようにします。

    • -streamAggr.dropInput は設定しないでください(デフォルトではルールに一致しないデータを保持し、集約データを書き込みます)。
    • -streamAggr.keepInput=true を設定して、一致した生サンプルも同時に書き込みます。これにより、生データと集約データのクエリを並べて比較できます。
  2. ステージ 2 — 検証(フェーズ 5 を参照)。少なくとも1時間、集約結果が生データに対する対応する sum(rate(...)) / histogram_quantile(...) と一致することを確認します。可能であれば、vmagent の再起動後も確認してください。

  3. ステージ 3 — 切り替え。-streamAggr.keepInput を削除します。これだけで、カーディナリティの削減が実現されます。デフォルトの SA の動作では、集約データを書き込み、リモート書き込み出力から一致した生サンプルを破棄します(ルールに一致しないサンプルだけが生データとして引き続き送信されます)。スクレイプ側で生メトリックを破棄する必要はありません。実際、破棄してはいけません。vmagent のパイプラインは scrape → relabel → dedup → stream aggregation → remote write のため、スクレイプ側で破棄するとアグリゲーターに必要な入力がなくなり、ルールは何も生成しません。

    ルールに一致しないサンプルもすべて破棄したい場合に限り、オプションで -streamAggr.dropInput=true を設定してください(まれなケースです。このリモート書き込み先が集約データ専用で、別のリモート書き込み先が完全解像度のデータを保持する場合に適しています)。3f フェーズで、入力の保持方法を3つ説明しています。

  4. ステージ 4 — 利用側の移行。ダッシュボードやアラートを、新しいメトリック名(自動追加されたサフィックス付き)に更新します。サフィックスの形式は :<interval>[_by_<labels>][_without_<labels>]_<output> です。どちらのラベルリストもアルファベット順にソートされます。without: [pod, instance, path] の場合は _without_instance_path_pod_、by: [function, region, cold_start] の場合は _by_cold_start_function_region_ になります。outputs: [quantiles(0.5, 0.99)] の場合、各出力系列にも quantile="<phi>" ラベルが付きます。元のメトリック名を維持したい場合は、keep_metric_names: true を設定してください(単一の出力でのみ有効です)。また、生データを完全に破棄するようにしてください。そうしないと、同じ名前で意味の異なる系列が2つ存在することになります。

再起動せずにリロードするには:

kill -SIGHUP $(pidof vmagent)
# or
curl -X POST http://vmagent:8429/-/reload
4d. 削減量の見積もり

各ルールについて、ユーザーに次の2つの数値を提示します。

  • 削減前の系列数(現状):TSDB ステータスの seriesCountByMetricName から取得するか、ユーザーから報告された値を使用します。
  • 削減後の系列数(実測値):フェーズ 2a の count(sum(rate(<metric>[5m])) by (<kept_labels>)) クエリを、現在のデータに対して実行したときの結果です。これが基準となる数値です。現在のデータに対して、ルールはおおむねこの数の出力系列を生成します。

実データにアクセスできない場合に限り、ラベル数の積による計算値を使用し、上限値として示します。

output_series ≤ Π(unique values of each label in `by`, including __name__)

ラベル値に相関がある場合(多くのルートでは、すべてのステータスコードが返されるわけではありません)、積による計算は過大評価になります。また、ユーザーが列挙していないラベルがある場合は、過小評価にもなり得ます。どちらの理由からも、実測値を優先してください。

妥当性チェック:集約後の実測値が集約前より大きい場合、by / without の指定が間違っています。想定よりも多くの固有の組み合わせを生成するラベルを残しています。


フェーズ 5:検証

ルールが有効になったら、次のチェックを実行します。ステージ 2(生データを保持)とステージ 3 の両方で実行してください。

5a. ストリーム集約の内部メトリクス

集約を実行する vmagent が公開するメトリクスを確認します(または、vmagent の Grafana ダッシュボードの Stream aggregation セクションで確認します)。

メトリクス示す内容
vm_streamaggr_matched_samples_total{name="..."}ルールに一致した入力サンプルです。想定されるスクレイプレート × 時系列数と一致するはずです。
vm_streamaggr_output_samples_total{name="..."}出力された集約済みサンプルです。おおよそ output_series / aggregation_interval です。
vm_streamaggr_flush_duration_seconds_bucket各間隔のフラッシュにかかった時間です。histogram_quantile(0.99, rate(vm_streamaggr_flush_duration_seconds_bucket[5m])) を計算すると、99 パーセンタイルを取得できます。interval を大幅に下回るはずです。interval に近い場合は、vmagent をスケールするか、interval を大きくしてください。
vm_streamaggr_ignored_samples_totalさまざまな理由で破棄されたサンプルです。too_old が急増している場合は、上流で遅延が発生している可能性があります。配信遅延については vm_streamaggr_samples_lag_seconds_bucket も参照してください。
vm_streamaggr_counter_resets_total観測されたカウンターリセットです。急増は、入力サンプルの重複または衝突を示します。上流の問題や、設定ミスのあるトポロジー(例:カウンタースタイルの出力を使用する HA レプリカ。「高可用性」セクションを参照)を確認してください。
5b. 集約済みデータと生データの相互確認(ステージ 2 のみ)

rate_sum で集約したカウンターの場合:

# raw
sum by (route, method, le) (rate(http_request_duration_seconds_bucket[5m]))

# aggregated (rate_sum is already a per-second rate — sum it, don't rate it again)
sum by (route, method, le) (http_request_duration_seconds_bucket:30s_without_pod_instance_rate_sum)

2 つの値は、数パーセント以内で推移するはずです。大きく乖離する場合は、次の原因が考えられます。

  • 複数の vmagent による衝突(フェーズ 3a、ケース E)。
  • ロードバランサーの背後で実行されている(フェーズ 3a、ケース D)。
  • dedup_interval とストレージの -dedup.minScrapeInterval が一致していない。
  • ヒストグラムの場合:サンプルの遅延。enable_windows を有効にするか、interval を大きくしてください。

rate_sum を使用したヒストグラムの場合:

# raw p99
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

# aggregated p99
histogram_quantile(0.99, sum by (le) (avg_over_time(http_request_duration_seconds_bucket:30s_without_pod_instance_rate_sum[5m])))
5c. 整合性チェック
  • count({__name__="<aggregated_name>"}) による出力時系列数が、フェーズ 4d の推定値とほぼ一致していること。
  • histogram_quantile(0.99, sum(rate(vm_streamaggr_flush_duration_seconds_bucket[1m])) by (vmrange)) が interval に近づいていることを示すアラートがないこと。
  • total/increase の出力の場合:カウンターが単調増加しており、リセットは vmagent の再起動時にのみ発生すること(flush_on_shutdown: true が設定されていない限り、これは想定どおりです)。

高可用性

耐障害性のために複数の vmagent レプリカを実行することはサポートされていますが、特定の条件が必要です。ストリーム集約は集約プロセス内で状態を保持し、その状態をレプリカ間で複製できないためです。

仕組み

複数のレプリカが同じ出力系列(ラベルセットも同一)を生成し、vmstorage の重複排除によって重複をまとめます。これは、次の条件をすべて満たす場合にのみ機能します。

  1. すべてのレプリカが同一の入力を受け取ること(スクレイプ対象が同じ、またはファンアウト方式のリモート書き込みであること。ラウンドロビン方式ではありません)。HA レプリカの前段にあるラウンドロビンロードバランサーはストリームを分割するため、HA の効果が損なわれ、さらにレプリカごとの集約結果が不完全になります。
  2. すべてのレプリカで同一の集約設定を使用すること。
  3. すべてのレプリカが同一のラベルを持つ出力サンプルを生成すること(レプリカごとのタグ付けはしないでください。詳細は後述)。
  4. vmstorage で -dedup.minScrapeInterval が設定されていること(通常は集約の interval と同じ値)。ルールごとの dedup_interval も一致させてください。
  5. 選択した集約出力が重複排除に対応していること(次のセクションを参照)。

HA レプリカに異なるラベル(-remoteWrite.label=vmagent=A、=B)を付けないでください。ラベルが異なると系列は別物になり、vmstorage は重複排除できません。その結果、ストレージ使用量が倍になり、コンシューマーはあらゆる場所で sum without (vmagent) を使う必要があります。「各レプリカにタグを付ける」方法は、シャーディングトポロジー(ケース F)で使うものです。この場合、各レプリカは入力の異なる一部を受け取り、それらを区別する必要があります。同じ入力を受け取る HA では使いません。

HA で安全な出力と安全でない出力
出力HA で安全か?理由
min、max、avg、last、sum_samples、count_samples、count_series、unique_samples、stddev、stdvar、quantiles(...)✅各区間の値は、その区間に観測されたサンプルから独立して計算されます。同じ入力を観測するレプリカは同じ値に収束するため、重複排除は問題なく行われます。
rate_sum、rate_avg、increase、increase_prometheus✅(ただし条件あり)出力は、レートまたは増分を表す区間ごとのゲージであり、単調増加するカウンターではありません。1 台のレプリカが再起動すると、系列ごとの内部状態が一時的に異なる場合があります(次のステールネス期間まで)が、出力値は収束します。
total、total_prometheus❌出力は、アグリゲーターのメモリに保持される単調増加カウンターです。1 台のレプリカが再起動すると、その出力は 0 にリセットされ、もう 1 台の出力は増加し続けます。重複排除では(系列、タイムスタンプ)ごとに 1 つのサンプルが選ばれます。同じ系列で異なるカウンターストリームを 2 つ出力すると、コンシューマーには交互の値が見え、rate() は逆方向の変化をカウンターリセットとして扱うため、目に見える変動やスパイクが発生します。異なる単調増加カウンター 2 つを適切に重複排除する方法はありません。
histogram_bucket❌バケットごとのカウンターは total と同様に累積します。同じ不一致の問題が発生します。

カウンター形式を必要とするコンシューマー(例:rate(...[5m]) を使う従来型のアラート)には、HA で安全な次の方法があります。

  • 集約を rate_sum に切り替え、クエリを更新します(rate_sum はすでに 1 秒あたりのレートなので、rate() を適用するのではなく、合計または平均を計算します)。
  • increase に切り替え、increase 形式のクエリを使用します。
  • 真の HA ではなく、単一のアグリゲーター(アクティブ/パッシブフェイルオーバー)を実行します。フェイルオーバー時の短い欠損を許容し、単調増加状態の不一致を避けます。
HA とシャーディング — 異なるパターンであり、同じディメンションで組み合わせないでください
  • シャーディング(ケース F):各レプリカは、入力の異なる一部を受け取ります。各レプリカは、by: にシャードキーを残す(推奨。クエリ時にロールアップします)か、一意のラベルでタグ付けすることで、それぞれ異なる出力系列を生成します。異なるレプリカが意図的に異なる値を生成します。
  • HA(ケース E):すべてのレプリカが同じ入力を受け取り、同じ出力ラベルを持つ出力を生成します。異なるレプリカが同じ値を生成し、vmstorage の重複排除によってまとめられます。

tier-1 をシャーディングし、tier-2 を HA にするトポロジーは有効です。tier-1 は instance ごとにシャーディングし、各 tier-1 シャードは 2 台の tier-2 HA レプリカにファンアウトします。この 2 台はどちらも同じシャードのストリームを受け取り、同一の出力を生成します。シャードキー(instance)は tier-2 の by: に含めます。tier-2 の HA レプリカは、それ以外の設定も同一にします。


出力の選択に応じたリスクに関する注意点

選択した出力に該当する場合にのみ表示してください。すべてを無条件に並べないでください。

出力 ∈ {total, total_prometheus, increase, increase_prometheus, rate_avg, rate_sum, histogram_bucket} の場合
  • 古さ:新しいサンプルがないまま staleness_interval(デフォルトでは 2 × interval)を過ぎると、系列ごとの状態がリセットされます。入力が散発的な場合(Lambda、cron ジョブ、バッチワーカー)は、再びサンプルが現れたときに値が急増しないよう、staleness_interval を長くしてください。古さを参照してください。
入力が散発的な場合(どの出力でも、特にゲージ)
  • 系列ごとの状態を持たない出力(例:quantiles、last、sum_samples)でも、サンプル間隔が長いと、その間のインターバルでは該当する出力系列が欠落するため、ダッシュボードのゲージに空白が生じます。想定される最大の無通信時間をカバーできるよう staleness_interval を長くするか、クエリ時に last_over_time(<aggregated>[<gap>]) を使用して短い欠落を補ってください。
  • 再起動後の最初のサンプル:total/increase は新しい系列が 0 から始まるものと想定するため、再起動後の最初のサンプルが大幅な増加として扱われます。ignore_first_sample_interval を設定するか、最初のサンプルをスキップする *_prometheus バリアントを使用してください。
  • カウンターのリセット(vm_streamaggr_counter_resets_total の値が増加):サンプルの重複、または vmagent レプリカ間の競合が原因です。集約前に重複排除(dedup_interval)するか、各出力系列が厳密に 1 つのアグリゲーターによって生成されるようにしてください。
ヒストグラムを集約する場合(どの出力でも)
  • バケットの整合性:ヒストグラムを集約できるのは、入力系列間で le(または vmrange)ラベルが同一の場合に限られます。バケット境界に不整合があると、誤った分位数が気付かれないまま算出されます。
  • サンプル遅延への感度:ヒストグラムは、まとめて更新する必要がある系列の論理グループです。サンプルのずれにより、誤った分位数が算出されます。対策:enable_windows: true を設定する、interval を長くする、vmagent と配信パイプラインに余裕を持たせる(キューを蓄積させない)。
  • +Inf バケット:入力に存在する必要があります。histogram_quantile では必須です。
  • le(または vmrange)でシャーディングしないでください:シャーディングされた vmagent トポロジー(ケース F)では、シャードキーをバケットラベルにしてはいけません。ヒストグラムは、le だけが異なる系列の論理グループです。整合性のあるバケットセットを生成できるよう、これらすべてのバケットを同じアグリゲーターに送る必要があります。le でシャーディングすると、1 つのヒストグラムのバケットがシャード間に分割され、各シャードは部分的なバケットセットを出力します。その結果、マージ後のデータに対する histogram_quantile() は、誤った値を気付かれないまま返します。シャードキーにはバケット以外のラベル(instance、kubernetes_namespace、__name__ など)を選び、すべての集約ルールの by: リストに le を含めてください。
複数の vmagent レプリカを実行する場合
  • HA トポロジー(各レプリカがすべてのサンプルを受信):同一の入力、同一の設定、同一の出力ラベルであれば、vmstorage の重複排除で重複が統合されます。レプリカを識別するラベルは付けないでください。これは重複排除に対応した出力でのみ機能します。高可用性セクションを参照してください。
  • シャーディングトポロジー(各レプリカが重複しない一部を担当する方式 — ケース F):シャードキーを by: に含めてください。含められない場合は、各レプリカに一意のラベルを付け、クエリ時に sum without (vmagent) (...) で再集約してください。クラスター構成を参照してください。
  • レプリカの前段にラウンドロビン型ロードバランサーを置くのは、どちらのパターンでも誤りです。HA では各レプリカが部分的なストリームを受信するため重複排除が機能せず、シャーディングでは決定性が失われます。シャーディングには決定的な -remoteWrite.shardByURL.labels=... を使用し、HA ではファンアウト(すべてのレプリカがすべてのサンプルを受信する方式)を使用してください。
dedup が有効な場合
  • dedup_interval は、ストレージの -dedup.minScrapeInterval と一致している必要があります。不一致の場合、(sum(rate(...)) などの)生のクエリ結果が集約メトリクスと一致しなくなります。よくある間違いを参照してください。

Kubernetes / VictoriaMetrics Operator

ストリーム集約の設定は、streamAggrConfig(グローバル)または remoteWrite[].streamAggrConfig(宛先ごと)を使って、VMAgent CRD で指定できます。

VMAgent CRD — グローバルなストリーム集約

vm-operator は、キャメルケースのフィールドを持つ streamAggrConfig を公開します。ここでは keepInput/dropInput はトップレベルのフィールドであり(-streamAggr.keepInput/-streamAggr.dropInput フラグに対応)、個々のルール内のフィールドではありません。フィールド名は Operator のバージョンによって異なるため、不明な場合は VMAgent CRD リファレンスで確認してください。

apiVersion: operator.victoriametrics.com/v1beta1
kind: VMAgent
metadata:
  name: vmagent
spec:
  replicaCount: 2                   # HA: both replicas see every sample; identical config below
  # Do NOT add a per-replica externalLabel for HA — it defeats vmstorage dedup. See HA section.
  extraArgs:
    streamAggr.dedupInterval: "30s" # match vmstorage -dedup.minScrapeInterval
  streamAggrConfig:
    keepInput: true                 # stage 2 — top-level, NOT inside a rule. Remove after verifying.
    rules:
      - match: 'http_request_duration_seconds_bucket'
        interval: 30s
        enable_windows: true
        without: [pod, instance]
        outputs: [rate_sum]          # HA-safe (per-interval rate, not monotonic counter)
  remoteWrite:
    - url: http://vmsingle.monitoring.svc:8429/api/v1/write
VMAgent CRD — リモート書き込み先ごとの集約
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMAgent
metadata:
  name: vmagent
spec:
  remoteWrite:
    - url: http://vmstorage-long-term.monitoring.svc:8429/api/v1/write
      streamAggrConfig:
        dropInput: true              # send ONLY aggregates to long-term store
        rules:
          - match: '{__name__=~".+"}'
            interval: 5m
            outputs: [last, max, min, count_samples]
    - url: http://vmstorage-hot.monitoring.svc:8429/api/v1/write
      # raw, full-resolution data to the hot store
2 層トポロジ — 第 1 層のシャード、第 2 層で集約(シャードキー = instance)

第 1 層の vmagent は instance でシャーディングし、第 2 層の集約用 vmagent に送ります。第 2 層の各インスタンスは、重複しない instance 値の範囲を担当するため、その範囲のインスタンスについてすべてのサンプルを受け取り、正しい集約結果を生成できます。ただし、by: に instance が保持されていることが条件です。

第 1 層(シャーディングのみ、集約なし):

vmagent-tier1 \
  -remoteWrite.url=http://vmagent-tier2-0:8429/api/v1/write \
  -remoteWrite.url=http://vmagent-tier2-1:8429/api/v1/write \
  -remoteWrite.url=http://vmagent-tier2-2:8429/api/v1/write \
  -remoteWrite.shardByURL=true \
  -remoteWrite.shardByURL.labels=instance

第 2 層(シャードキーを考慮した集約):

# tier2-stream-aggr.yaml
- match: 'http_request_duration_seconds_bucket'
  interval: 30s
  enable_windows: true
  by: [instance, method, status_code, route, le]   # `instance` MUST stay — it's the shard key
  outputs: [rate_sum]
vmagent-tier2 \
  -remoteWrite.url=http://vmstorage:8429/api/v1/write \
  -streamAggr.config=/etc/vmagent/tier2-stream-aggr.yaml

instance を含めないサービス単位の集約も必要な場合は、第 2 層の各インスタンスが一部のインスタンスしか持たないため、第 2 層に 2 つ目の集約ルールを設定するのではなく、クエリ時に sum without (instance) (http_request_duration_seconds_bucket:30s_by_...:rate_sum) を実行してください。

ベアメタル / Docker — コマンドラインフラグ
vmagent \
  -remoteWrite.url=http://vmsingle:8429/api/v1/write \
  -streamAggr.config=/etc/vmagent/stream-aggr.yaml \
  -streamAggr.dedupInterval=30s \
  -streamAggr.keepInput=true                        # stage 2 only

HA(すべてのレプリカが同じターゲットをスクレイプし、重複排除に対応した出力のみを行う)の場合、レプリカごとのラベルは付けないでください。vmstorage の重複排除で重複を統合するには、ラベルが同一である必要があります。各レプリカが重複しない範囲を担当するシャード型トポロジ(ケース F)では、シャードキーがすでに by: に保持されている場合を除き、レプリカごとのラベルを追加してください。

  -remoteWrite.label=vmagent=$HOSTNAME   # sharded ≠ HA; do NOT use this for HA

実例

ユーザー:「600 個の Pod があり、http_request_duration_seconds_bucket が急増しています。ラベルは pod, instance, method, path, status_code, le, route、ルートは 40 個、le バケットは 10 個です。すでに vmagent を経由しており、サービス全体の p99 だけが必要です。」

判断:

判断項目選択理由
ゲート通過 — vmagent を経由しており、Pod ごとの情報が失われても問題ない
間隔30s15s のスクレイプ間隔の 2 倍で、5m のクエリウィンドウに一致します。
出力rate_sum + enable_windows: trueヒストグラムバケットに推奨されるパターンです。利用側は histogram_quantile です。
without[pod, instance, path](route が正規化された形式であることを前提とします)不要なものをすべて削除し、method、status_code、route、le を保持します。
配置先既存の vmagentすでに経由しています。レプリカが 1 つだけであることを確認するか、HA タグを使用してください。
スケールいいえルールは 1 つだけなので、現在の vmagent で問題ありません。

推定値:4 methods × 5 status × 40 routes × 11 buckets (incl. +Inf) ≈ 8.8K series。対して、生データは 600 pods × 8.8K ≈ 5.3M です。約 600 倍削減できます。

設定:

- name: http_duration_p99
  match: 'http_request_duration_seconds_bucket'
  interval: 30s
  enable_windows: true
  without: [pod, instance, path]
  outputs: [rate_sum]

ロールアウト:-streamAggr.keepInput=true を有効にしてデプロイし、Phase 5b のクロスチェック用クエリで 1 時間確認してから、このフラグを削除します。keepInput を削除すると、一致した生データはデフォルトでリモートストレージに書き込まれなくなり、カーディナリティの削減が実現します。スクレイプ側でメトリクスを削除しないでください。アグリゲーターが依存する入力が失われます。

利用側のクエリの更新 — サフィックスではラベルがアルファベット順に並ぶことに注意してください(instance, path, pod)。

# Before
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

# After
histogram_quantile(0.99,
  sum by (le, route) (
    avg_over_time(http_request_duration_seconds_bucket:30s_without_instance_path_pod_rate_sum[5m])
  )
)

よくある間違い

間違い修正方法
YAML に keep_input / drop_input を記述するこれらはコマンドラインフラグです(-streamAggr.keepInput、-streamAggr.dropInput、またはリモート書き込みごとのバリアント)。
ゲージに total を使用するtotal はカウンター用です。ゲージには last / avg / sum_samples を使用してください。
カウンターに sum_samples を使用するカウンターの生の値を合計するため、意味がありません。total、increase、または rate_sum を使用してください。
rate_sum の出力を rate() でクエリするrate_sum はすでに秒あたりのレートです。合計または平均を求めてください。レートを計算しないでください。
rate() を使わずに total の出力をクエリするtotal は単調増加するカウンターです。rate() または increase() を使用してください。
間隔がスクレイプ間隔より短いバケットが空になり、出力が不安定になります。interval ≥ 2× scrape_interval に設定してください。
HA レプリカに個別の vmagent=... ラベルを付けるvmstorage の重複排除が機能せず、ストレージ上の時系列が恒久的に二重になります。HA では、レプリカは同一のラベルを出力する必要があります。重複排除によって統合されます。「各レプリカにタグを付ける」パターンは、HA ではなくシャーディングトポロジー向けです。
total または total_prometheus 出力を使って HA レプリカを実行するカウンターの状態はプロセス内に保持されるため、レプリカが 1 つでも再起動すると値に差が生じ、利用側で値の急変が発生します。rate_sum または increase に切り替えるか、アグリゲーターを 1 つだけ実行してください。
ラウンドロビンロードバランサーの背後で HA レプリカを実行する各レプリカがストリームの一部しか受信しないため、集計が不完全になり、重複排除もできません。ファンアウト(各レプリカがすべてのサンプルを受信)または決定的なシャーディングを使用してください。
ラウンドロビンロードバランサーの背後にアグリゲーターを配置する各 vmagent が一部しか受信しないため、集計が不完全になります。まずラベルで決定的にシャーディングする(-remoteWrite.shardByURL.labels)か、単一のアグリゲーターで受信してください。
without: でシャードキーを除外する(または by: に含めない)シャーディングされた vmagent各シャードは入力の一部に対する不完全な集計を生成します。異なるシャードの出力が同じ時系列に衝突し、合計が気付かれないまま誤った値になります。すべての下流ルールでシャードキーを by: に残し、追加のロールアップはクエリ時に行ってください。
ヒストグラムを le(または vmrange)でシャーディングする1 つのヒストグラムのバケットが複数のシャードに分割され、各シャードが部分的なバケットセットを出力します。統合した結果に対する histogram_quantile() は、気付かれないまま誤った値になります。ヒストグラムはバケット以外のラベル(instance、kubernetes_namespace、__name__)でシャーディングし、le を by: に残してください。
dedup_interval とストレージの -dedup.minScrapeInterval が異なる生データと集計データのクエリ結果に差が生じます。同じ値にしてください。
enable_windows を有効にせずにヒストグラムを集計するサンプルの不整合により、分位数が誤った値になります。enable_windows: true に設定してください。
生データも引き続き流れているのに、出力を元のメトリック名に変更する同じ名前で異なる時系列が 2 つできます。入力を破棄する(-streamAggr.dropInput)か、サフィックスを残してください。
レコーディングルールごとに集計ルールを 1 つ作成する各ルールがすべてのサンプルを走査します。interval と by / without が共通するルールは、リスト形式の match: を使ってまとめてください。
断続的な入力に対して staleness_interval を低く設定しすぎるサンプル間で状態がリセットされ、合計が誤った値になります。想定される間隔をカバーできるよう、値を大きくしてください。
複数の出力で keep_metric_names: true を使用する許可されていません。出力が 1 つの場合に限り有効です。
アラートルール/レコーディングルールの更新を忘れる古いルールは、すでに存在しないメトリック(または、さらに悪いことに、存在はするものの生データが空のメトリック)をクエリします。

クイックリファレンス

# Skeleton — start here, fill in `match`, `interval`, `without`/`by`, `outputs`.
- name: <name>
  match: '<selector>'
  interval: 30s
  without: [pod, instance]
  outputs: [rate_sum]              # see output picker
  # enable_windows: true           # histograms; delayed inputs
  # dedup_interval: 30s            # match storage dedup
  # staleness_interval: 4m         # for sporadic inputs

出力の選び方(ほとんどの場合):

  • カウンターで、レートの合計が必要:rate_sum
  • カウンターで、単調増加カウンターが必要:total
  • ヒストグラムのバケット:rate_sum + enable_windows: true
  • ゲージの最新値:last
  • ソース全体のゲージの合計:sum_samples
  • ゲージのパーセンタイル:quantiles(0.5, 0.9, 0.99)

判断の目安:

  • 間隔 = max(2 × scrape_interval、最短のコンシューマーウィンドウ / 2)
  • ソースごとの値が利用されない場合は、pod/instance を削除します
  • HA vmagents:入力、設定、出力ラベルがすべて同一の場合、vmstorage が重複排除します。レプリカにタグを付けないでください。HA 対応の出力のみ使用します(total/total_prometheus/histogram_bucket は不可)。
  • シャード化された vmagents:シャードキーを by: に含めるか、各レプリカにタグを付け、クエリ時にロールアップします。
  • ラウンドロビン LB の背後にある場合:処理を中断してください。決定的なシャーディング(-remoteWrite.shardByURL.labels)を使用するか、ファンアウトします。
  • レコーディングルールを置き換える場合:interval をルールの評価間隔に設定し、リスト形式の match: を使って複数のルールを 1 つの設定エントリーにまとめます。

コメント