カーディナリティ予算とラベル設計の規律
監視コストの暴走を設計で防ぐ。時系列数を直積から見積もり、許容上限を予算化し、ラベル選定・集約・リラベリングで配分する規律を原理から学べます。
- 1.時系列数はメトリクスごとに「各ラベルの値域の直積」で決まる。許容上限を先に決め、ラベルはその予算内に収める設計対象として扱う。
- 2.ユーザーID・リクエストID・URL全体・メールアドレスなど値域が事実上無限の次元は禁止ラベル。集計軸として使う有限・低基数の属性だけをラベルにする。
- 3.予算超過はドロップ/keep/リラベリングで入口を絞り、recording ruleで事前集約する。コストはメモリ・索引・クエリに時系列数で線形に乗る。
カーディナリティ予算という考え方
監視基盤の運用コストは、突き詰めれば アクティブな時系列(time series)の本数 にほぼ比例します。にもかかわらず、時系列はメトリクスを出す側が自由にラベルを足すたびに増えるため、放置すれば青天井です。そこで先に 「この基盤が抱えてよい時系列の総数」を上限として決め、各メトリクス/各チームにその枠を配分する 運用が カーディナリティ予算(cardinality budget) です。
予算は抽象論ではなく具体的な数字です。たとえば「1ノードあたりアクティブ系列数 200万まで」「1メトリクスあたり10万系列まで」のように上限を置き、超えたら設計をやり直す。基数爆発の機構そのものは /devops/metrics-cardinality/ に譲り、本稿は その上限をどう決め、ラベル設計でどう守るか に集中します。
時系列数の見積もり:直積で上限を計算する
1本のメトリクスが生む時系列数は、付与するラベルそれぞれの値域(取りうる値の種類数)の 直積(デカルト積) で決まります。設計時点でこの掛け算を必ず試算します。
時系列数 = メトリクス数 × ラベルAの値域 × ラベルBの値域 × …
たとえば http_requests_total に method(5種)・status(8種)・route(120種)を付け、それを300インスタンスで動かすと、
5 × 8 × 120 × 300 = 1,440,000 系列
このメトリクス1本で既に144万系列です。ここに route のテンプレート化を怠って生URLを入れたり、user_id を1本足したりすると、直積の項が1つ増えるだけで桁が変わります。ラベルの追加は加算ではなく乗算 なので、見積もりは常に最悪ケース(各ラベル値域の積)で行うのが鉄則です。
試算に入れる「値域」は理論値ではなく 保持期間内に実際に出現しうるユニーク値の数 です。route を正規化していれば有限ですが、/user/12345 のようにパスにIDが混じると値域は実質ユーザー数になります。チャーン(インスタンス入れ替え)で増える instance 系の値も、保持期間ぶん累積する点を見落とさないこと。
禁止すべき高基数次元
予算を守る第一歩は、値域が事実上無限の属性をラベルにしない ことです。これらは単独で予算を食い尽くすため、原則として禁止ラベルとして扱います。
| 禁止すべき次元 | 値域の性質 | 本来の置き場所 |
|---|---|---|
| user_id / account_id | ユーザー数ぶん。増え続ける | ログ・トレースの属性、exemplar |
| request_id / trace_id | リクエストごとに一意。無限 | トレース本体、exemplar |
| full_url(クエリ込み) | パラメータの組み合わせで爆発 | route テンプレートに正規化 |
| email / IP / session_id | 識別子。高基数かつ個人情報 | 構造化ログのフィールド |
| timestamp / 連番 | 時刻や採番で常に新しい値 | ラベルにせずサンプル値・時刻軸へ |
判定基準は単純です。「その属性で系列を区別したいか、それとも個別の事例を追跡したいだけか」 を問う。前者(集計軸)ならラベル、後者(追跡)なら高基数情報はラベルではなく構造化ログやトレース側に載せます。個別事例を追う情報の置き場所については /devops/structured-logging-wide-events/ と /devops/distributed-tracing/ を参照してください。
高基数IDをラベル化する最大の動機は「あとでドリルダウンしたくなるかも」です。しかしラベルは 全時系列の直積に効く ため、使うかどうか不明な次元を1本足すだけで予算が枯渇します。ドリルダウンが必要なら、メトリクスを増やさず exemplar でトレースへ橋渡しするのが正解です。
ラベル選定の規律
予算内に収めるラベルの選び方には、いくつかの明確な基準があります。
- 集計軸として実際にクエリで使うか:ダッシュボードやアラートの
by()/without()に現れない次元は載せない。「表示しないラベル」は純粋なコストです。 - 値域が有限かつ安定か:取りうる値が事前に列挙でき、運用中に増え続けないこと。
statusやmethodは安定、pod_nameは不安定(チャーンで増える)。 - 直交する次元か:
regionとavailability_zoneのように一方が他方を含意する場合、両方を独立ラベルにすると無駄な直積が生まれる。冗長な次元は片方に寄せる。 - 値が運用者の制御下にあるか:外部入力(ユーザー指定の文字列など)をそのままラベルにしない。攻撃者が任意のラベル値を注入すれば、意図的な基数爆発(カーディナリティDoS)が成立します。
これらを満たさない属性は、ラベルではなくメトリクス値そのもの(カウントやヒストグラム)か、ログ・トレースのフィールドに移します。
予算管理の実装:ドロップ・keep・リラベリング
設計で抑えきれない、あるいは送信側が勝手に高基数ラベルを付けてくる場合に備え、取り込みパイプラインで予算を強制 します。Prometheus なら metric_relabel_configs が最後の防衛線です。
metric_relabel_configs:
# 1) 高基数ラベルを削除(値は捨てるが系列は名前で残る)
- regex: "user_id|session_id|request_id"
action: labeldrop
# 2) 不要な系列ごと捨てる(特定メトリクスを取り込まない)
- source_labels: [__name__]
regex: "go_gc_.*"
action: drop
# 3) 必要なものだけ keep(許可リスト方式)
- source_labels: [__name__]
regex: "http_.*|app_.*"
action: keep
ここで重要なのは labeldrop と drop の違いです。labeldrop はラベルを消すことで複数系列を1本に畳む(直積の項を減らす)。一方 drop は系列そのものを取り込まない。前者は基数を下げ、後者は不要メトリクスを丸ごと止めます。許可リスト(keep)方式にすると、新しいメトリクスはデフォルトで入らないため、予算が無断で侵食されません。
リラベルでラベルを落としても、同じラベル集合を持つ系列が複数あれば自動的にマージされるわけではなく、衝突した系列は値が不定になり警告が出ます。labeldrop で次元を畳むときは、畳んだ後に系列が一意になるか(残ったラベルだけで区別できるか)を必ず確認します。区別が必要な集約は、ドロップではなく次節の recording rule で行います。
事前集約とコストの内訳
予算を恒常的に下げるもう一段が recording rule(事前集約) です。高基数の生メトリクスは短い保持期間に留め、ダッシュボードは集約済みの低基数メトリクスを参照させます。
# 生メトリクス(高基数)から、集約軸だけ残した低基数系列を事前生成
record: route:http_requests:rate5m
expr: sum by (route, status) (rate(http_requests_total[5m]))
sum by (route, status) で instance や method の直積を畳むため、結果系列数が激減し、クエリのたびに巨大な生系列を走査する必要もなくなります。なぜこれがコストに効くかは、時系列数が 3つのコストに線形に乗る からです。
| コスト要素 | 時系列数との関係 | 予算超過時の症状 |
|---|---|---|
| メモリ(head) | アクティブ系列ごとに常駐チャンク | OOM・スクレイプ停止 |
| 索引(posting list) | ラベル値の種類数に比例して肥大 | 起動・WAL再生の遅延 |
| クエリ | マッチ系列数ぶん走査 | ダッシュボード・アラート評価のタイムアウト |
時系列数を予算内に保つことは、この3コストを同時に抑えることに等しい。だからこそ「あとで効率化」ではなく、ラベルを足す瞬間に直積を試算して予算と照合する 規律が必要になります。アラート評価が予算超過で遅延する具体的な影響は /devops/alerting-theory/、基盤全体の容量配分は /devops/capacity-planning/ と併せて設計します。
まとめ
- カーディナリティ予算は 「抱えてよい時系列の総数」を上限として先に決め、各メトリクスへ配分する 運用。ラベルは予算という制約下の設計対象である。
- 時系列数は 各ラベル値域の直積 で決まる。ラベル追加は乗算的に効くため、見積もりは常に最悪ケースの積で行う。
user_id・request_id・生URL・emailなど 値域が無限に近い次元はラベル禁止。集計軸に使う有限・安定・直交・制御下の属性だけをラベルにする。- 予算強制は 入口の
labeldrop/drop/keepで、恒常削減は recording rule の事前集約 で行う。時系列数はメモリ・索引・クエリに線形に乗るため、これを抑えることが直接コストを抑える。
DevOps/インフラ Article
カーディナリティ予算とラベル設計の規律を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Prometheus
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5
導入後に効く点
ユーザーID・リクエストID・URL全体・メールアドレスなど値域が事実上無限の次元は禁止ラベル。集計軸として使う有限・低基数の属性だけをラベルにする。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「Prometheus / メトリクス」に近いか確認する。
- 強みである「時系列数はメトリクスごとに「各ラベルの値域の直積」で決まる。許容上限を先に決め、ラベルはその予算内に収める設計対象として扱う。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。