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を飼い慣らすということは、ツールの限界を知り、その中でいかに「意味のある信号」だけを抽出するかという、極めて高度な知的なゲームなのだ。
君たちが設計したメトリクスが、夜中のアラートを減らし、エンジニアを救うことを願っている。コードを書くときと同じように、監視設計にも「美学」を持て。