victoriametrics-cardinality-analysis
victoriametrics-cardinality-analysis について
このスキルは、TSDB の状態、メトリクスの利用統計、ラベル値のパターンを使って VictoriaMetrics の時系列カーディナリティを分析します。未使用メトリクス、高カーディナリティラベル、ヒストグラムの負荷、系列の変動を特定し、再ラベル付けやストリーム集約の設定案を示します。
主な機能
- 01TSDB のカーディナリティ統計を収集
- 02未使用・低頻度クエリのメトリクスを特定
- 03高カーディナリティラベルと値の傾向を確認
- 04ヒストグラム負荷と系列変動を分析
- 05再ラベル付けとストリーム集約を提案
活用シーン
- 01保存時系列数の増加を調査
- 02削除候補となる未使用メトリクスを特定
- 03リクエスト ID や SQL を含むラベルを検出
- 04メトリクスの再ラベル付けと集約を計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ victoriametrics/ skills --skill victoriametrics-cardinality-analysisインストール後、Claude Code で/victoriametrics-cardinality-analysisを入力して読み込まれたか確認します。
詳細
VictoriaMetrics のカーディナリティ分析
VictoriaMetrics のカーディナリティを体系的に分析します。TSDB のステータス、メトリクスの使用状況統計、ラベル値のパターンを収集し、ユーザーがそのまま適用できる具体的な relabeling とストリーム集約の設定を含む、構造化されたレポートを作成します。
目標は、最も効果の大きい最適化の機会を見つけることです。誰もクエリしていないメトリクス、監視上の価値がないのにカーディナリティを急増させるラベル、データ衛生上の問題を示すパターン(ラベルとしてのエラーメッセージ、SQL テキスト、UUID など)を特定します。
環境
victoriametrics-query スキルと同じ環境変数を使用します。
# $VM_METRICS_URL - base URL
# 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 is required)
# Remote: export VM_CURL_CONFIG="$HOME/.config/victoriametrics/curl.conf"
# Local: leave unset (defaults to /dev/null - no auth)
ワークフロー
フェーズ 1:データ収集
データを並行して収集するため、1 回の応答でサブエージェントを 3 つ起動します。各サブエージェントへのプロンプトには、上記の curl 認証パターンと環境変数の参照を含める必要があります。
ユーザーがスコープ(job、namespace、メトリクスのプレフィックス)を指定した場合は、TSDB ステータスクエリの match[] パラメーターと、ラベルクエリのシリーズセレクターに渡します。
サブエージェント 1:TSDB の概要
エージェント名:cardinality-tsdb | 説明:"TSDB のカーディナリティ統計を収集"
クエリ 1 — 昨日のシリーズ(最近入れ替わったシリーズを把握):
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/tsdb?topN=50&date=$(date -d 'yesterday' +%Y-%m-%d)" | jq '.data'
昨日の統計をクエリします。TSDB 全体をスキャンすることなく、今日のデータより広い範囲(すでに入れ替わっている可能性のあるシリーズを含む)を確認できます。
クエリ 2 — 今日のアクティブなシリーズ:
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/tsdb?topN=50" | jq '.data'
クエリ 3 — 既知の高カーディナリティラベルに注目:
for label in pod instance container path url user_id request_id session_id trace_id le name; do
echo "=== focusLabel=$label ===" && \
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/tsdb?topN=20&focusLabel=$label" | \
jq --arg l "$label" '{label: $l, focus: .data.seriesCountByFocusLabelValue}'
done
返却内容:構造を保持したすべての生の JSON。各クエリの totalSeries、totalLabelValuePairs、seriesCountByMetricName、seriesCountByLabelName、seriesCountByLabelValuePair を含めます。
サブエージェント 2:メトリクスの使用状況統計
エージェント名:cardinality-usage | 説明:"未使用およびクエリ頻度の低いメトリクスを特定"
クエリ 1 — 一度もクエリされていないメトリクス:
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/metric_names_stats?le=0&limit=500" | jq '.'
クエリ 2 — クエリ頻度の低いメトリクス(クエリ合計数が 5 以下):
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/metric_names_stats?le=5&limit=500" | jq '.'
クエリ 3 — 統計の概要(追跡期間):
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/metric_names_stats?limit=1" | \
jq '{statsCollectedSince: .statsCollectedSince, statsCollectedRecordsTotal: .statsCollectedRecordsTotal}'
エンドポイントがエラーを返す場合は、vmstorage で storage.trackMetricNamesStats が有効になっていない可能性があります。その旨を返却内容に記載して処理を続けてください。TSDB ステータスデータだけでも分析は可能です。
クエリ 4 — クロスチェック:「未使用」メトリクスはアラートルールで参照されているか?
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/rules" | jq '[.data.groups[].rules[].query]'
ルールのクエリからメトリクス名を抽出します。「未使用」メトリクスがアラートルールまたは記録ルールに含まれている場合、安全に削除できません。間接的にクエリされています。
返却内容:アラートルールとの相互参照を含む未使用メトリクス。各メトリクスを次のように分類します。
- 削除しても安全:一度もクエリされておらず、どのルールにも含まれていない
- ルールからのみ使用:ダッシュボードからは一度もクエリされていないが、ルールで参照されている — 意図を確認する
- 使用頻度が低い:クエリ数が少なく、アクセス頻度が低い可能性がある(例:月次レポート)
サブエージェント 3:ラベルパターンの調査
エージェント名:cardinality-labels | 説明:「問題のあるパターンがないかラベル値を調査します」
すべてのデータは TSDB ステータスエンドポイントから取得します。/api/v1/labels や /api/v1/label/.../values は使用しないでください。
クエリ 1 — ラベルのカーディナリティ概要(ユニーク値数+シリーズ数):
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/tsdb?topN=50" | \
jq '{labelValueCountByLabelName: .data.labelValueCountByLabelName, seriesCountByLabelName: .data.seriesCountByLabelName}'
labelValueCountByLabelName は、ユニーク値数の多い順にラベルを返します(ラベルごとの /values カウントに代わるものです)。
seriesCountByLabelName は、各ラベルが含まれるシリーズ数を示します。
クエリ 2 — focusLabel を使ったカーディナリティの高いラベルのサンプル値:
クエリ 1 でユニーク値が 100 を超える各ラベルについて、サンプル値を取得します。
for label in <top labels from Query 1>; do
echo "=== focusLabel=$label ===" && \
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/tsdb?topN=20&focusLabel=$label" | \
jq --arg l "$label" '{label: $l, topValues: .data.seriesCountByFocusLabelValue}'
done
seriesCountByFocusLabelValue は、シリーズ数の多い順にラベル値を返します。値の名前を使って、問題のあるパターンを検出します。
クエリ 3 — カーディナリティの高いラベルと値の組み合わせ:
curl -q --config "${VM_CURL_CONFIG:-/dev/null}" -s \
"$VM_METRICS_URL/api/v1/status/tsdb?topN=50" | \
jq '.data.seriesCountByLabelValuePair'
どの label=value の組み合わせが特に多くのシリーズを生み出しているかを示します。
パターン検出 — focusLabel のサンプルからラベル値を分類します。
| パターン | 正規表現のヒント | 示す内容 |
|---|---|---|
| UUID | [0-9a-f]{8}-[0-9a-f]{4}- | リクエスト/セッション/トレース ID がラベルとして使われています |
| IP アドレス | \d+\.\d+\.\d+\.\d+ | クライアントまたはポッドごとの IP を追跡しています |
| 長い文字列(50 文字超) | 長さを確認 | エラーメッセージ、SQL、スタックトレース |
| SQL キーワード | SELECT|INSERT|UPDATE|DELETE|FROM|WHERE | クエリテキストがラベルとして保存されています |
| ID を含む URL パス | /api/.*/[0-9a-f]+ | サニタイズされていない HTTP パス |
| タイムスタンプ | エポックまたは ISO8601 | 時刻の値がラベルとして使われています(上限なし) |
| スタックトレース | at .*\.(java|go|py): | エラーの詳細がラベルとして使われています |
返却内容:ユニーク値数の多い順に並べたラベルの表。検出したパターン、focusLabel のサンプル値、シリーズへの影響を含めます。
フェーズ 2:分析
すべてのサブエージェントから結果が返されたら、調査結果をまとめて分類します。ここが分析の中核です。機械的なフィルタリングではなく、判断を適用してください。
カテゴリ 1:未使用のメトリクス(すぐに効果が出る改善)
メトリクスの使用状況統計と TSDB のシリーズ数を照合します。
- 削除候補:
queryRequestsCount=0で、アラートルールや記録ルールで使われておらず、シリーズ数が 100 超 - 要確認候補:
queryRequestsCount=0だがルールで参照されているもの。ルールがまだ必要か確認します - 優先度低:
queryRequestsCount≤5でシリーズ数が少ないもの。設定変更の手間をかける価値はありません
シリーズ数の降順に並べます。未使用のメトリクスのうち、最も規模が大きいものほど改善効果も大きくなります。
カテゴリ 2:カーディナリティの高いラベル
ユニーク値が過剰に多く、シリーズの爆発的な増加を招くラベルです。
| ラベルパターン | 評価 | 代表的な対処法 |
|---|---|---|
user_id、customer_id、account_id | メトリクスラベルにしてはいけません。ログ/トレースに属する情報です | ラベルを削除 |
request_id、session_id、trace_id、span_id | 相関 ID であり、メトリクスラベルにしてはいけません | ラベルを削除 |
error、error_message、reason、status_message | 上限のない文字列です | ラベルを削除するか、エラーコードに置き換え |
sql、query、command、statement | ラベル内のクエリテキストに上限がありません | ラベルを削除 |
path、url、uri、endpoint | サニタイズされていない場合、上限がありません | リラベルして正規化するか、ストリーム集計から除外 |
pod、container | k8s では一般的ですが、頻繁に変化します | ポッドごとの情報が不要なら、ストリーム集計から除外 |
instance | ノードメトリクスでは一般的ですが、アプリケーションメトリクスでは無駄です | アプリケーションレベルのメトリクスではストリーム集計から除外 |
le(ヒストグラムのバケット) | 粒度の細かいバケットは、ラベルの組み合わせごとのシリーズ数を増やします | バケット数を減らす |
各調査結果について、影響を見積もります:(このラベルを持つシリーズ数)-(このラベルを持たないシリーズ数)≈ 削減できるシリーズ数。
カテゴリ 3:ヒストグラムの肥大化
_bucket で終わるメトリクスを確認します。
leのユニーク値はいくつありますか?- バケットを 1 つ追加するごとに、シリーズ数は(ラベルの組み合わせ数)倍になります
- ほとんどのバケットが空、または重複しているヒストグラムを探します
カテゴリ 4:系列のチャーン
昨日と今日の統計を比較します:
- 比率が 3:1 を超える場合、Pod の再起動、デプロイ、短時間で終了するジョブによる大きなチャーンが示唆されます
- リラベルでは直接修正できませんが、
dedup_intervalまたは-search.maxStalenessIntervalの調整を検討する機会を示しています
フェーズ 3:レポート
構造化されたレポートにまとめます。すべての調査結果に、影響の推定値と具体的な修正設定を含めてください。
次のテンプレートを使用します:
## VictoriaMetrics Cardinality Report — <date>
### Overview
| Metric | Value |
|--------|-------|
| Total active series (today) | X |
| Total series (yesterday) | X |
| Churn ratio (yesterday / today) | X:1 |
| Unique metric names | X |
| Stats tracking since | <date> |
### 1. Unused Metrics
**Potential savings: ~X series (Y% of total)**
| Metric | Series | Last Queried | In Alert Rules | Action |
|--------|--------|-------------|----------------|--------|
| ... | ... | never | no | Drop |
| ... | ... | never | yes — verify | Check rule |
<details>
<summary>Relabeling config to drop unused metrics</summary>
```yaml
# Add to vmagent metric_relabel_configs (VMServiceScrape or global)
metric_relabel_configs:
- source_labels: [__name__]
regex: "metric1|metric2|metric3"
action: drop
```
</details>
### 2. High-Cardinality Labels
**Potential savings: ~X series (Y%)**
| Label | Unique Values | Top Affected Metrics | Pattern | Action |
|-------|--------------|---------------------|---------|--------|
| user_id | 50,000 | http_requests_total | UUID | Drop |
| path | 10,000 | http_request_duration | URL paths | Aggregate |
| error_message | 5,000 | app_errors_total | Long strings | Drop |
<details>
<summary>Drop labels that should never be in metrics</summary>
```yaml
metric_relabel_configs:
- regex: "user_id|request_id|session_id|trace_id|error_message|sql_query"
action: labeldrop
```
</details>
<details>
<summary>Stream aggregation for high-cardinality HTTP labels</summary>
```yaml
# vmagent stream aggregation config
- match: '{__name__=~"http_request.*"}'
interval: 1m
without: [path, instance, pod]
outputs: [total]
# drop_input: true # enable after verifying aggregated output
- match: '{__name__=~"http_request_duration.*_bucket"}'
interval: 1m
without: [pod, instance]
outputs: [total]
keep_metric_names: true
```
</details>
<details>
<summary>Normalize URL paths via relabeling</summary>
```yaml
metric_relabel_configs:
- source_labels: [path]
regex: "/api/v1/users/[^/]+"
target_label: path
replacement: "/api/v1/users/:id"
- source_labels: [path]
regex: "/api/v1/orders/[^/]+"
target_label: path
replacement: "/api/v1/orders/:id"
```
</details>
### 3. Histogram Optimization
**Potential savings: ~X series (Y%)**
| Metric | Bucket Count | Recommendation |
|--------|-------------|----------------|
| ... | 30 | Reduce to standard 11 buckets |
### 4. Series Churn
| Observation | Value |
|------------|-------|
| Yesterday / today ratio | X:1 |
| Primary driver | Pod restarts / short-lived jobs |
### Summary
| Category | Est. Series Saved | % of Total | Effort |
|----------|------------------|-----------|--------|
| Drop unused metrics | X | Y% | Low — relabeling only |
| Drop bad labels | X | Y% | Low — labeldrop |
| Stream aggregation | X | Y% | Medium — new config |
| Histogram reduction | X | Y% | Low — bucket filtering |
| **Total** | **X** | **Y%** | |
### Implementation Priority
1. **[Low effort]** Drop unused metrics — pure relabeling, no data loss risk
2. **[Low effort]** Drop labels that should never be in metrics (IDs, messages, SQL)
3. **[Medium effort]** Stream aggregation for high-cardinality HTTP/app metrics
4. **[Medium effort]** Histogram bucket reduction
実際の調査結果に合わせてテンプレートを調整してください。該当する調査結果がないセクションは省略し、重要な調査結果があるセクションは詳しく説明してください。
修正方法のリファレンス
リラベル(metric_relabel_configs)
スクレイプ時またはリモート書き込み時に適用されます。変更は新しいデータにすぐ反映されます。
メトリクス全体を破棄する:
metric_relabel_configs:
- source_labels: [__name__]
regex: "metric_to_drop|another_metric"
action: drop
ラベルを破棄する:
metric_relabel_configs:
- regex: "label_to_drop|another_label"
action: labeldrop
ラベル値を正規化する(一意な値を減らす):
metric_relabel_configs:
- source_labels: [path]
regex: "/api/v1/users/[^/]+"
target_label: path
replacement: "/api/v1/users/:id"
ストリーム集約
vmagent レベルで適用されます。ストレージへの書き込み前に、処理中のデータを集約します。 ドキュメント:https://docs.victoriametrics.com/victoriametrics/stream-aggregation/
メトリクスのセマンティクスを維持しながらラベルを削除する:
- match: '{__name__=~"http_.*"}'
interval: 1m
without: [instance, pod]
outputs: [total]
カウンターを集約する(カーディナリティの高いディメンションを削除する):
- match: 'http_requests_total'
interval: 30s
without: [path, user_id]
outputs: [total]
ヒストグラムを集約する:
- match: '{__name__=~".*_bucket"}'
interval: 1m
without: [pod, instance]
outputs: [quantiles(0.5, 0.9, 0.99)]
keep_metric_names: true
よく使われる出力関数:
| 関数 | 用途 | 例 |
|---|---|---|
total | カウンター(累積合計) | リクエスト数 |
sum_samples | ゲージの合計 | Pod 全体のメモリ使用量 |
count_samples | サンプル数 | 報告を行っているインスタンス数 |
last | 最新のゲージ値 | 現在の温度 |
min, max | 極値 | レイテンシのピーク |
avg | 平均値 | CPU 使用率の平均 |
quantiles(0.5, 0.9, 0.99) | 分布の推定 | レイテンシのパーセンタイル |
histogram_bucket | ヒストグラムの再バケット化 | バケットの粒度を下げる |
重要:カウンターには total を、ゲージには last/avg/sum_samples を使用してください。ゲージに total を使うと、意味のない累積合計が生成されます。
Kubernetes での適用方法
| 方法 | CRD/設定 | スコープ |
|---|---|---|
metric_relabel_configs | VMServiceScrape/VMPodScrape .spec.metricRelabelConfigs | スクレイプ対象ごと |
| グローバルリラベル | VMAgent -remoteWrite.relabelConfig | すべてのメトリクス |
| ストリーム集約 | VMAgent -remoteWrite.streamAggr.config | リモート書き込みされるすべてのメトリクス |
| リモート書き込み先ごとの SA | VMAgent .spec.remoteWrite[].streamAggrConfig | 宛先ごと |
よくある間違い
| 間違い | 修正方法 |
|---|---|
| アラートで使用されているメトリクスを破棄する | 破棄する前に、必ず /api/v1/rules と照合してください |
テストせずに drop_input: true を設定する | まず集約結果が想定どおりであることを確認してください |
ゲージを total でストリーム集約する | ゲージには last、avg、または sum_samples を使用してください |
keep_metric_names: true を忘れる | 指定しないと、出力に長い自動生成サフィックスが付きます |
ヒストグラムから le ラベルを完全に削除する | 特定の le 値だけを削除し、ラベル自体は削除しないでください |
| レコーディングルールの依存関係を考慮しない | アラートルールとレコーディングルールの両方を確認してください |
| テストせずにリラベルを適用する | -dryRun フラグを使用するか、まず単一のスクレイプ対象でテストしてください |