【テクニカル・上級編】PromQLにおけるアノテーションとラベルのアンチパターン:Cardinality爆発を防ぐデータモデリングの極意 – 運用監視・オブザーバビリティ活用バイブル

Prometheusの心臓を止めるな:カーディナリティ爆発を制するデータモデリングの極意

Prometheusを運用していて、ある日突然、クエリがタイムアウトし、OOM Killで再起動を繰り返す地獄を見たことはないか?多くのエンジニアが「メトリクスは多ければ多いほど良い」という誤謬に陥る。だが、Prometheusの真の設計思想は「データポイントの質と構造」にある。

今日は、Prometheusのパフォーマンスを決定づける「カーディナリティ(基数)」の深淵に切り込み、いかにしてこの時限爆弾を解体するか、その設計哲学を伝授する。

—

1. カーディナリティ爆発が引き起こす「不可逆な死」

Prometheusの内部エンジン(TSDB)は、各系列を `MetricName{Label1=Value1, Label2=Value2…}` というIDでハッシュマップ管理している。

カーディナリティ爆発とは、一意なラベル組み合わせの爆発的増加を指す。これが起きると、以下の負の連鎖が始まる:

1. インデックスの肥大化: メモリ上のインデックスが物理メモリを食い尽くし、スワップが発生。クエリ速度が指数関数的に低下する。
2. WAL(Write Ahead Log)の書き込み遅延: ディスクI/Oがボトルネックになり、データ収集の取りこぼしが発生する。
3. クエリエンジンの麻痺: `sum by (service) (rate(…))` のようなクエリが発行された瞬間、何百万もの系列をスキャンする必要が生じ、CPUが100%に張り付く。

結論を言おう。Prometheusはデータベースではない。「時系列データのサマライザ」だ。

—

2. 絶対にやるな:ラベル設計の「禁忌」

初心者がやりがちな「NGデータ」をラベルに混ぜる行為。これは運用上の自殺行為だ。

  • UUID / ユーザーID: これがラベルに入った瞬間、ユーザー数だけ時系列データが生成される。例えば100万ユーザーがいれば、100万系列。Prometheusは瞬殺される。
  • タイムスタンプ: `request_time_2023_10_27_…` のような文字列をラベルにするのは論外だ。
  • 高頻度に変更されるURLパラメータ: `?id=123` をそのままラベルにすると、無限の組み合わせが生成される。

なぜこれがダメなのか

Prometheusのラベルは「検索の軸」であるべきだ。「識別子」ではない。識別が必要なら、それはログ(LokiやElasticsearch)の領域だ。メトリクスは「状態の集約」に徹するべきである。

—

3. 現場で使える「集約とリネーミング」のテクニック

カーディナリティを抑えるには、取り込み段階(Relabeling)での「情報圧縮」が肝となる。

A. `relabel_config` による不要ラベルの削除・正規化

スクレイピング時に不要な動的パラメータを削ぎ落とす。

scrape_configs:

  • job_name: ‘api-server’

relabel_configs:
# 1. 不要なUUID系ラベルを削除

  • action: labeldrop

regex: ‘request_id|user_uuid’

# 2. URLパスからパラメータを正規化(例: /user/123 -> /user/:id)

  • source_labels: [__metrics_path__]

regex: ‘/user/.’
target_label: ‘url_path’
replacement: ‘/user/:id’

B. `label_replace` で情報を抽象化する

クエリ時に動的にラベルを書き換え、高解像度すぎるデータを粗い解像度へ落とし込む。

高解像度なパスを抽象化して集計する
sum by (url_path) (
rate(http_requests_total[5m])
)
↑ これが重い場合、PrometheusのRecording Ruleで事前に集約しておくことが鉄則。

—

4. アーキテクトだけが知っている「極限の最適化」

Recording Ruleによる「クエリの事前計算」

生データに対して複雑な集計クエリを投げるのは、戦場で銃弾を数えるようなものだ。
Recording Ruleを活用し、結果をあらかじめ別のメトリクスとして書き出せ。

groups:

  • name: aggregation_rules

rules:

  • record: job:http_requests:rate5m

expr: sum by (job, method) (rate(http_requests_total[5m]))

指標の密度を管理する(Prometheus CLI API)

Prometheusの内部状態を監視せよ。定期的に以下のクエリを投げ、カーディナリティのトップランナーを特定し、排除するスクリプトをCI/CDに組み込むべきだ。

系列数が多いメトリクスランキングを取得
topk(10, count by (__name__) ({__name__=~”.+”}))

—

5. 伝説のエンジニアからの提言

オブザーバビリティの本質は「何が起きているか」を最短距離で把握することにある。「データが足りない」という不安から、何でもかんでもラベルに突っ込むのは、監視ではなく「データの墓場」を作っているのと同じだ。

1. メトリクスは集約してナンボ: 粒度が細かすぎるデータは、PrometheusではなくLokiへ投げろ。
2. ラベルは「意味のあるカテゴリ」に限定せよ: `environment`, `region`, `service`, `version` 程度で十分だ。
3. インフラの限界を設計に反映させろ: Prometheusインスタンスを巨大化させるな。機能やサービスごとにシャーディングし、`Thanos` や `Cortex` を使って横にスケールさせよ。

Prometheusを飼い慣らすということは、ツールの限界を知り、その中でいかに「意味のある信号」だけを抽出するかという、極めて高度な知的なゲームなのだ。

君たちが設計したメトリクスが、夜中のアラートを減らし、エンジニアを救うことを願っている。コードを書くときと同じように、監視設計にも「美学」を持て。

タイトルとURLをコピーしました