Prometheus | PromQL

PromQL 完全ガイド — Prometheus Query Language の全容

1. はじめに

1.1 PromQL とは何か

PromQL(Prometheus Query Language)は、時系列データベースである Prometheus に蓄積されたメトリクスデータを検索・集約・変換するために設計された関数型クエリ言語である。Prometheus は Cloud Native Computing Foundation(CNCF)の卒業プロジェクトであり、クラウドネイティブ環境におけるモニタリングのデファクトスタンダードとして広く採用されている。

PromQL は SQL や他のクエリ言語とは根本的に異なるアプローチを取る。SQL がリレーショナルデータに対する操作を記述するのに対し、PromQL は時系列データに特化しており、時間軸に沿ったデータの選択、フィルタリング、集約、数学的変換を直感的に記述できる。

1.2 PromQL の位置づけ

Prometheus エコシステムにおいて、PromQL は以下の場面で使用される:

  • アドホッククエリ: Prometheus の Web UI(Expression Browser)でのリアルタイムなメトリクス探索
  • Grafana ダッシュボード: 可視化パネルのデータソースクエリとして
  • アラートルール: Prometheus の Alerting Rules で閾値条件を定義
  • Recording Rules: よく使うクエリを事前に計算して新しい時系列として保存
  • HTTP API: /api/v1/query および /api/v1/query_range エンドポイント経由でのプログラマティックなアクセス

1.3 Prometheus のデータモデル

PromQL を理解するには、まず Prometheus のデータモデルを把握する必要がある。

時系列(Time Series)

Prometheus のすべてのデータは時系列として保存される。各時系列は以下の要素で構成される:

<metric_name>{<label_name>=<label_value>, ...}  <value>  <timestamp>

例:

http_requests_total{method="GET", handler="/api/v1/users", status="200"} 1027 1609459200
  • メトリクス名(Metric Name): http_requests_total — 測定対象を表す識別子
  • ラベル(Labels): {method="GET", handler="/api/v1/users", status="200"} — 時系列を多次元的に区別するキーバリューペア
  • 値(Value): 1027 — float64 型の数値
  • タイムスタンプ: 1609459200 — ミリ秒精度の Unix タイムスタンプ

メトリクスの種類

Prometheus には 4 つの基本メトリクスタイプがある:

メトリクスタイプ説明用途例
Counter単調増加する累積値。リセット時にのみ 0 に戻るリクエスト数、エラー数、送受信バイト数
Gauge任意に増減する瞬時値温度、メモリ使用量、同時接続数
Histogram観測値をバケットに分類し、累積カウントと合計値を記録リクエストレイテンシ、レスポンスサイズ
Summaryクライアント側で分位数を計算して記録リクエストレイテンシ(事前計算された分位数)

サンプル(Sample)

各時系列は時間軸に沿った(タイムスタンプ, 値)のペアの列で構成される。個々のペアを「サンプル」と呼ぶ。

http_requests_total{method="GET"} @ timestamp_1 = 100
http_requests_total{method="GET"} @ timestamp_2 = 105
http_requests_total{method="GET"} @ timestamp_3 = 112

Prometheus はデフォルトで 15 秒間隔(scrape_interval)でターゲットからメトリクスを収集(スクレイプ)し、これらのサンプルを TSDB(Time Series Database)に格納する。

1.4 本記事の構成

本記事は以下の構成で PromQL の全容を解説する:

  1. はじめに(本章) — PromQL の概要とデータモデル
  2. 基本的なセレクタとフィルタリング — 時系列の選択方法
  3. データ型と演算子 — PromQL の型システムと演算
  4. 関数リファレンス — 主要な組み込み関数の解説
  5. 集約演算子 — データのグループ化と集約
  6. ベクトルマッチング — 複数の時系列間の演算
  7. サブクエリとオフセット — 高度な時間操作
  8. Recording Rules — クエリの事前計算
  9. Alerting Rules — アラート条件の定義
  10. Histogram と Summary の詳細 — 分布データの扱い
  11. 実践的なクエリパターン — 現場で使えるクエリ集
  12. パフォーマンスとベストプラクティス — 効率的なクエリの書き方
  13. Prometheus のアーキテクチャ — 内部構造と PromQL の実行
  14. エコシステムと互換性 — Thanos, Cortex, Mimir との連携
  15. まとめとリファレンス — 総括と参考資料

2. 基本的なセレクタとフィルタリング

2.1 インスタントベクトルセレクタ(Instant Vector Selector)

インスタントベクトルセレクタは、指定した時刻における各時系列の最新のサンプル1つを返す。最も基本的なクエリ形式である。

メトリクス名による選択

http_requests_total

このクエリは http_requests_total というメトリクス名を持つすべての時系列の最新値を返す。ラベルの組み合わせが異なる時系列がそれぞれ返される。

ラベルマッチャーによるフィルタリング

ラベルマッチャーを使用して、特定の条件に合致する時系列のみを選択できる。

http_requests_total{method="GET"}

4 種類のラベルマッチャーが利用可能:

マッチャー意味
=完全一致{method="GET"}
!=不一致{method!="OPTIONS"}
=~正規表現一致{method=~"GET|POST"}
!~正規表現不一致{handler!~"/api/internal/.*"}

正規表現は RE2 構文に基づき、完全一致(暗黙的に ^...$ が付加される)で評価される。

複数ラベルマッチャーの組み合わせ

http_requests_total{method="GET", handler="/api/v1/users", status=~"2.."}

複数のマッチャーはすべて AND 条件として評価される。上記は「GET メソッドで、/api/v1/users ハンドラーで、ステータスコードが 200 番台」のリクエスト数を返す。

__name__ ラベルによるメトリクス名の選択

メトリクス名は内部的に __name__ という特殊ラベルとして扱われる。以下は等価である:

http_requests_total
{__name__="http_requests_total"}

正規表現を使えば、複数のメトリクス名を一度に選択できる:

{__name__=~"http_requests_total|http_request_duration_seconds_count"}

あるいは、特定のプレフィックスを持つメトリクスを選択:

{__name__=~"node_cpu_.*"}

注意: 空のラベルマッチャー {} や、すべてに一致する {__name__=~".*"} は許可されない。少なくとも1つの空文字列に一致しないマッチャーが必要。

2.2 レンジベクトルセレクタ(Range Vector Selector)

レンジベクトルセレクタは、各時系列について指定した時間範囲内のすべてのサンプルを返す。インスタントベクトルセレクタに時間範囲(duration)を付加することで記述する。

http_requests_total{method="GET"}[5m]

上記は過去 5 分間のすべてのサンプルを返す。返されるデータ型はレンジベクトル(Range Vector)であり、直接グラフ化することはできない。rate()increase() などの関数の引数として使用する。

時間範囲の指定

単位意味
msミリ秒[500ms]
s[30s]
m[5m]
h時間[1h]
d[7d]
w[2w]
y[1y]

複合指定も可能:[1h30m](1時間30分)

適切な時間範囲の選び方

レンジベクトルの時間範囲は、スクレイプ間隔の少なくとも 4 倍以上を推奨する。デフォルトのスクレイプ間隔が 15 秒の場合、最低でも [1m] を使用する。これは、スクレイプの欠落やタイミングのずれを補償するためである。

一般的なガイドライン:

  • scrape_interval: 15s → 最低 [1m]、推奨 [5m]
  • scrape_interval: 30s → 最低 [2m]、推奨 [5m]
  • scrape_interval: 60s → 最低 [4m]、推奨 [5m][10m]

2.3 オフセット修飾子(Offset Modifier)

offset 修飾子を使用すると、現在時刻からの相対的な過去の時点を基準にクエリを評価できる。

# 1時間前の値
http_requests_total{method="GET"} offset 1h

# 1時間前から5分間のレンジベクトル
http_requests_total{method="GET"}[5m] offset 1h

これは前日比較やトレンド分析に有用:

# 現在のリクエストレートと1日前のリクエストレートの比較
rate(http_requests_total[5m]) / rate(http_requests_total[5m] offset 1d)

2.4 @ 修飾子(At Modifier)

@ 修飾子を使用すると、特定の Unix タイムスタンプの時点でクエリを評価できる。

# 特定のタイムスタンプ(2024-01-01 00:00:00 UTC)での値
http_requests_total @ 1704067200

# start() と end() を使った範囲クエリ内での参照
http_requests_total @ start()
http_requests_total @ end()

start()end() はレンジクエリ(/api/v1/query_range)のコンテキストで、クエリの開始時刻と終了時刻を参照する。

2.5 負のオフセット(Negative Offset)

Prometheus 2.26 以降では、将来方向のオフセットも指定可能(--enable-feature=promql-negative-offset が必要):

http_requests_total offset -5m

これは主に Recording Rules や特殊なユースケースで使われる。

2.6 実践例:基本セレクタの活用

# 1. 特定のジョブからのすべてのメトリクス
{job="prometheus"}

# 2. 特定のインスタンスのCPU使用率メトリクス
node_cpu_seconds_total{instance="server01:9100", mode="idle"}

# 3. 本番環境のHTTPエラーレスポンス
http_requests_total{environment="production", status=~"5.."}

# 4. 特定のネームスペースのPodメトリクス(Kubernetes)
container_memory_usage_bytes{namespace="default", pod=~"myapp-.*"}

# 5. 内部APIを除くすべてのハンドラー
http_request_duration_seconds_count{handler!~"/internal/.*"}

3. データ型と演算子

3.1 PromQL のデータ型

PromQL は以下の 4 つのデータ型を持つ:

1. インスタントベクトル(Instant Vector)

各時系列について、ある一時点のサンプル(タイムスタンプと値のペア)を1つだけ含むデータ型。PromQL のほとんどの演算はこの型に対して行われる。

# インスタントベクトルを返す
http_requests_total{method="GET"}

返却例:

http_requests_total{method="GET", handler="/api/users", status="200"} 1027
http_requests_total{method="GET", handler="/api/users", status="404"} 42
http_requests_total{method="GET", handler="/api/items", status="200"} 5839

2. レンジベクトル(Range Vector)

各時系列について、指定した時間範囲内のすべてのサンプルを含むデータ型。rate() などの関数の入力として使用される。

# レンジベクトルを返す
http_requests_total{method="GET"}[5m]

返却例:

http_requests_total{method="GET", handler="/api/users"} 
  1000 @1609459100
  1005 @1609459115
  1012 @1609459130
  1020 @1609459145
  1027 @1609459160

3. スカラー(Scalar)

単一の数値(float64)。ラベルやタイムスタンプを持たない。

# スカラーリテラル
42
3.14
-0.5
Inf
-Inf
NaN

4. 文字列(String)

単一の文字列値。現在の PromQL では直接使用される場面はほとんどなく、将来の拡張のために予約されている。

"hello world"

3.2 算術演算子(Arithmetic Operators)

PromQL は以下の二項算術演算子をサポートする:

演算子説明
+加算node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes
-減算node_filesystem_size_bytes - node_filesystem_avail_bytes
*乗算rate(http_requests_total[5m]) * 100
/除算node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
%剰余time() % 3600
^べき乗2 ^ 10

ベクトルとスカラーの演算

# バイトからメガバイトへの変換
node_memory_MemTotal_bytes / 1024 / 1024

# パーセンテージの算出
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) 
  / node_memory_MemTotal_bytes * 100

# 摂氏から華氏への変換(ゲージメトリクスの場合)
temperature_celsius * 9 / 5 + 32

ベクトル同士の演算

# メモリ使用率(%)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
  / node_memory_MemTotal_bytes * 100

# ディスク使用率(%)
(node_filesystem_size_bytes - node_filesystem_avail_bytes)
  / node_filesystem_size_bytes * 100

ベクトル同士の演算では、ラベルのマッチングが行われる(詳細はベクトルマッチングの章で解説)。

3.3 比較演算子(Comparison Operators)

演算子説明
==等しい
!=等しくない
>より大きい
<より小さい
>=以上
<=以下

フィルタリングモード(デフォルト)

比較演算子はデフォルトでフィルタとして機能し、条件を満たさない時系列を除外する:

# CPU使用率が80%を超えるインスタンス
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80

# メモリ空き容量が1GB未満のノード
node_memory_MemAvailable_bytes < 1024*1024*1024

# ディスク使用率が90%以上のファイルシステム
(node_filesystem_size_bytes - node_filesystem_avail_bytes)
  / node_filesystem_size_bytes * 100 >= 90

bool モード

bool 修飾子を使用すると、フィルタリングの代わりに 0(false)または 1(true)を返す:

# 各時系列に対して閾値チェックの結果を0/1で返す
http_requests_total > bool 1000

# 例: 値が100を超えるか?
node_load1 > bool 2.0

返却例:

node_load1{instance="server01:9100"} 1   # 2.0を超えている
node_load1{instance="server02:9100"} 0   # 2.0以下

3.4 論理/集合演算子(Logical/Set Operators)

論理演算子はインスタントベクトル同士でのみ使用可能:

演算子説明
and積集合(両方に存在する時系列)
or和集合(いずれかに存在する時系列)
unless差集合(左にあり右にない時系列)

and(積集合)

# HTTPエラーレートが高く、かつリクエスト数が多いハンドラー
rate(http_requests_total{status=~"5.."}[5m]) > 0.1
  and
rate(http_requests_total[5m]) > 10

両方の条件を満たす時系列のみが返される(左辺の値が使用される)。

or(和集合)

# メモリまたはCPUのアラート条件に該当するインスタンス
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.1
  or
(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) > 0.9

両方のセットの時系列が返される。重複する場合は左辺の値が優先される。

unless(差集合)

# メンテナンス中のインスタンスを除外したアラート
rate(http_requests_total{status=~"5.."}[5m]) > 0.5
  unless
maintenance_mode == 1

左辺の時系列のうち、右辺にマッチしないものだけが返される。

3.5 演算子の優先順位

高い順に:

  1. ^
  2. *, /, %, atan2
  3. +, -
  4. ==, !=, <=, <, >=, >
  5. and, unless
  6. or

括弧 () を使用して優先順位を明示的に制御できる:

# 括弧を使った明確な表記
(rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])) * 100 > 5

3.6 三項演算的パターン

PromQL には三項演算子(condition ? then : else)は存在しないが、andor を組み合わせて類似のロジックを実現できる:

# 条件に応じて異なる閾値を適用
(
  rate(http_requests_total[5m]) > 100
    and
  rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.01
)
  or
(
  rate(http_requests_total[5m]) <= 100
    and
  rate(http_requests_total{status=~"5.."}[5m]) > 1
)

4. 関数リファレンス

PromQL には豊富な組み込み関数が用意されている。ここでは主要な関数をカテゴリ別に解説する。

4.1 レート計算関数

rate(v range-vector)

Counter メトリクスの1秒あたりの平均増加率を計算する。Counter のリセット(再起動など)を自動的に検出・補正する。

# 過去5分間の平均リクエストレート(リクエスト/秒)
rate(http_requests_total[5m])

# ネットワーク受信の平均スループット(バイト/秒)
rate(node_network_receive_bytes_total[5m])

重要なポイント:

  • Counter メトリクスにのみ使用すること(Gauge には使用しない)
  • 返される値は常に1秒あたりの割合
  • 指定した範囲内に最低2つのサンプルが必要
  • 外挿(extrapolation)によりレンジの境界でのデータ欠損を補正

irate(v range-vector)

Counter メトリクスの瞬間的な増加率を、レンジ内の直近2サンプルのみから計算する。

# 瞬間的なリクエストレート
irate(http_requests_total[5m])

rate vs irate の使い分け:

特性rateirate
計算方法レンジ全体の平均直近2サンプル間
結果の特性平滑化された値瞬間的な変動を反映
グラフでの見え方スムーズスパイキー
推奨用途アラート、ダッシュボードトラブルシューティング
Recording Rules使用可能推奨しない

ベストプラクティス: アラートルールには常に rate を使用する。irate はノイズが多く、一時的なスパイクで不要なアラートを発火させる可能性がある。

increase(v range-vector)

指定した時間範囲内での Counter の増加量を計算する。概念的に rate() * 時間範囲の秒数 と等しい。

# 過去1時間のリクエスト総数
increase(http_requests_total[1h])

# 過去24時間のエラー数
increase(http_requests_total{status=~"5.."}[24h])

注意: increase() は外挿を行うため、整数値の Counter に対しても小数値を返すことがある。

4.2 集約に関連する関数

sum_over_time(v range-vector)

時間範囲内のすべてのサンプル値の合計を返す。

# 過去1時間のサンプル値の合計
sum_over_time(http_requests_total[1h])

avg_over_time(v range-vector)

# 過去1時間の平均CPU使用率
avg_over_time(node_cpu_usage_percent[1h])

その他の *_over_time 関数群

# 最小値
min_over_time(node_memory_MemAvailable_bytes[1h])

# 最大値
max_over_time(node_cpu_usage_percent[1h])

# 標準偏差
stddev_over_time(http_request_duration_seconds[1h])

# 標準偏差(母集団)
stdvar_over_time(http_request_duration_seconds[1h])

# カウント(サンプル数)
count_over_time(up[1h])

# 最新の値
last_over_time(temperature[1h])

# 直近の値が存在する場合のみ返す
present_over_time(up[5m])

# 分位数
quantile_over_time(0.95, http_request_duration_seconds[1h])

4.3 数学関数

# 絶対値
abs(delta(temperature_celsius[1h]))

# 天井関数(切り上げ)
ceil(rate(http_requests_total[5m]))

# 床関数(切り捨て)
floor(rate(http_requests_total[5m]))

# 丸め
round(rate(http_requests_total[5m]), 0.1)  # 0.1単位で丸め

# 自然対数
ln(rate(http_requests_total[5m]) + 1)

# 常用対数
log2(node_memory_MemTotal_bytes)
log10(node_memory_MemTotal_bytes)

# 指数関数
exp(prediction_linear(disk_usage[1h], 3600))

# 平方根
sqrt(histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])))

# クランプ(範囲制限)
clamp(cpu_usage_percent, 0, 100)
clamp_min(free_space_bytes, 0)
clamp_max(temperature, 100)

# 符号
sgn(delta(temperature[1h]))  # -1, 0, or 1

4.4 日付・時刻関数

# 現在のUnixタイムスタンプ
time()

# 分(0-59)
minute()

# 時(0-23)
hour()

# 日(1-31)
day_of_month()

# 曜日(0=日曜, 6=土曜)
day_of_week()

# 年内の日数(1-366)
day_of_year()

# 年内の週番号
days_in_month()

# 月(1-12)
month()

# 年
year()

# 各サンプルのタイムスタンプを値として返す
timestamp(up)

実用例:営業時間内のアラート制御

# 平日の営業時間(9:00-18:00 JST = 0:00-9:00 UTC)内のみアラート
(
  day_of_week() >= 1 and day_of_week() <= 5
    and
  hour() >= 0 and hour() < 9
)
* rate(http_requests_total{status=~"5.."}[5m]) > 0.05

4.5 ラベル操作関数

label_replace()

正規表現を使ってラベル値を変換・追加する。

label_replace(
  up{job="prometheus"},
  "short_instance",     # 新しいラベル名
  "$1",                 # 置換テンプレート
  "instance",           # ソースラベル
  "(.*):.*"             # 正規表現(キャプチャグループ)
)

結果:up{job="prometheus", instance="server01:9090", short_instance="server01"}

label_join()

複数のラベル値を結合して新しいラベルを作成する。

label_join(
  up{job="prometheus"},
  "full_id",          # 新しいラベル名
  "-",                # セパレータ
  "job",              # ソースラベル1
  "instance"          # ソースラベル2
)

結果:up{job="prometheus", instance="server01:9090", full_id="prometheus-server01:9090"}

4.6 予測関数

predict_linear(v range-vector, t scalar)

線形回帰に基づいて、t 秒後の予測値を返す。

# 4時間後のディスク空き容量を予測
predict_linear(node_filesystem_avail_bytes[6h], 4*3600)

# ディスクが24時間以内に枯渇する予測のアラート
predict_linear(node_filesystem_avail_bytes[6h], 24*3600) < 0

deriv(v range-vector)

Gauge メトリクスの1秒あたりの変化量を単純線形回帰で推定する。

# 温度の変化傾向(度/秒)
deriv(temperature_celsius[1h])

delta(v range-vector)

Gauge メトリクスの最初と最後のサンプル間の差分を返す。

# 過去2時間の温度変化
delta(temperature_celsius[2h])

4.7 ソート・情報関数

# 昇順ソート
sort(rate(http_requests_total[5m]))

# 降順ソート
sort_desc(rate(http_requests_total[5m]))

# ベクトルの要素数
scalar(count(up))

# スカラーをベクトルに変換
vector(42)

# 値が存在しない時系列(stale)の除外
# present_over_time で過去5分間にサンプルがある系列のみ選択
count(present_over_time(up[5m]))

4.8 ヒストグラム関数

histogram_quantile(φ scalar, b instant-vector)

ヒストグラムバケットから分位数を推定する。詳細はヒストグラムの章で解説。

# 99パーセンタイルレイテンシ
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# 50パーセンタイル(中央値)
histogram_quantile(0.5, rate(http_request_duration_seconds_bucket[5m]))

4.9 三角関数(Prometheus 2.31+)

acos(v), acosh(v), asin(v), asinh(v), atan(v), atanh(v)
cos(v), cosh(v), sin(v), sinh(v), tan(v), tanh(v)

# 二変数アークタンジェント(演算子として)
atan2(vector_y, vector_x)

# π定数
pi()

# ラジアンと度の変換
deg(v)  # ラジアン → 度
rad(v)  # 度 → ラジアン

5. 集約演算子(Aggregation Operators)

集約演算子はインスタントベクトルの要素をグループ化し、各グループに対して集約関数を適用する。

5.1 集約演算子一覧

演算子説明
sum合計
min最小値
max最大値
avg平均値
groupすべての値を 1 にして返す
stddev標本標準偏差
stdvar標本分散
count要素数
count_values各値の出現回数
bottomk下位 k 個の要素
topk上位 k 個の要素
quantile分位数
limitkランダムに k 個を制限(Prometheus 2.41+)
limit_ratioランダムに比率で制限(Prometheus 2.41+)

5.2 bywithout

集約演算子の動作を制御する 2 つの句がある:

by — 指定したラベルでグループ化

# ジョブ別のアクティブインスタンス数
count by (job) (up == 1)

# ステータスコード別のリクエストレート
sum by (status) (rate(http_requests_total[5m]))

# 複数ラベルでのグループ化
sum by (method, handler) (rate(http_requests_total[5m]))

by で指定されなかったラベルは結果から除去される。

without — 指定したラベルを除外してグループ化

# instance ラベルを除外して集約(他のすべてのラベルでグループ化)
sum without (instance) (rate(http_requests_total[5m]))

# instance と pod を除外
avg without (instance, pod) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

withoutby の逆で、指定したラベル以外のすべてのラベルでグループ化する。ラベル構造が不明な場合や、将来ラベルが追加されても対応できるようにしたい場合に有用。

by vs without の選択指針

  • 結果に残したいラベルが少ない → by を使用
  • 除外したいラベルが少ない → without を使用
  • ラベル構造が変わる可能性がある → without が安全

5.3 各演算子の詳細と使用例

sum — 合計

# クラスタ全体の総リクエストレート
sum(rate(http_requests_total[5m]))

# ネームスペース別のメモリ使用量合計(Kubernetes)
sum by (namespace) (container_memory_usage_bytes)

# ジョブ別の総帯域幅
sum by (job) (rate(node_network_transmit_bytes_total[5m])) * 8  # bps変換

avg — 平均

# インスタンス全体の平均CPU使用率
avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100

# ジョブ別の平均レスポンスタイム
avg by (job) (rate(http_request_duration_seconds_sum[5m]) 
  / rate(http_request_duration_seconds_count[5m]))

count — カウント

# アクティブなターゲット数
count(up == 1)

# ジョブ別のターゲット数
count by (job) (up)

# エラーが発生している固有のハンドラー数
count(rate(http_requests_total{status=~"5.."}[5m]) > 0)

count_values — 値別カウント

# Prometheusバージョンの分布
count_values("version", prometheus_build_info)

結果例:

{version="2.45.0"} 3
{version="2.44.0"} 2
{version="2.43.1"} 1

topkbottomk

# リクエストレート上位5のハンドラー
topk(5, rate(http_requests_total[5m]))

# メモリ使用量が最も少ない3つのPod
bottomk(3, container_memory_usage_bytes{container!=""})

# エラーレート上位10のサービス
topk(10, sum by (service) (rate(http_requests_total{status=~"5.."}[5m])))

注意: topkbottomk はラベルを保持する。by 句と組み合わせる場合、各グループ内で上位/下位 k 個が選ばれるのではなく、全体から k 個が選ばれた後にグループ化される。

quantile — 分位数

# 全インスタンスのCPU使用率の90パーセンタイル
quantile(0.9, rate(node_cpu_seconds_total{mode="idle"}[5m]))

# ジョブ別の95パーセンタイルメモリ使用量
quantile by (job) (0.95, process_resident_memory_bytes)

group — グループ化のみ

# 存在するラベルの組み合わせを確認(値はすべて1)
group by (job, instance) (up)

5.4 集約の組み合わせ

複数の集約演算子を組み合わせることで、複雑な分析が可能:

# 各ジョブのインスタンス数が5未満のジョブを検出
count by (job) (up) < 5

# 平均と最大の乖離が大きい時系列を発見
(max by (job) (rate(http_requests_total[5m])) 
  - avg by (job) (rate(http_requests_total[5m])))
/ avg by (job) (rate(http_requests_total[5m])) > 3

# 全体に占める各ハンドラーのリクエスト割合
sum by (handler) (rate(http_requests_total[5m])) 
  / scalar(sum(rate(http_requests_total[5m]))) * 100

5.5 limitklimit_ratio(Prometheus 2.41+)

大量の時系列がある場合に、結果をランダムにサンプリングする:

# ランダムに10個の時系列を返す
limitk(10, http_requests_total)

# 約50%の時系列をランダムに返す
limit_ratio(0.5, http_requests_total)

これらは探索的な分析や、大量の時系列を含むダッシュボードでのプレビューに有用。

6. ベクトルマッチング(Vector Matching)

2つのインスタントベクトル間で演算を行う際、PromQL はラベルに基づいて時系列をマッチングする。このメカニズムを理解することは、PromQL を効果的に使う上で極めて重要である。

6.1 一対一マッチング(One-to-One)

デフォルトでは、両辺のベクトルから同じラベルセットを持つ要素同士がマッチングされる。

# エラーレートの計算
rate(http_requests_total{status=~"5.."}[5m])
  /
rate(http_requests_total[5m])

この場合、method, handler, instance, job などすべてのラベルが一致する組み合わせのみが計算される。

ignoring キーワード

特定のラベルを無視してマッチングする:

# status ラベルを無視してマッチング
rate(http_requests_total{status=~"5.."}[5m])
  / ignoring(status)
rate(http_requests_total[5m])

ここで問題が生じる。右辺では status ラベルの異なる値(200, 201, 404, 500 など)を持つ複数の時系列が同じマッチングキーになり、一対多の関係になる。これを解決するのが group_left / group_right である。

on キーワード

指定したラベルのみでマッチングする(ignoring の逆):

# method と handler のみでマッチング
rate(http_errors_total[5m]) 
  / on(method, handler)
rate(http_requests_total[5m])

6.2 多対一 / 一対多マッチング

group_left(多対一:左辺が「多」側)

# ステータスコード別のエラー割合を計算
# 左辺(多): 各statusごとのレート → 右辺(一): 全statusの合計レート
rate(http_requests_total{status=~"5.."}[5m])
  / ignoring(status) group_left
sum without(status) (rate(http_requests_total[5m]))

処理の流れ:

  1. ignoring(status) により、status ラベルを除外してマッチング
  2. 左辺は status=500, status=502, status=503 など複数の時系列を持つ(多)
  3. 右辺は status を sum で集約済みなので1つの時系列(一)
  4. group_left により、左辺の各時系列が右辺の1つの時系列とマッチ

group_right(一対多:右辺が「多」側)

# group_right の例(右辺が多)
sum without(status) (rate(http_requests_total[5m]))
  * ignoring(status) group_right
(rate(http_requests_total{status=~"5.."}[5m]) * 0 + 1)

実際にはほとんどの場合 group_left が使われる。

group_left でのラベルコピー

group_left(label1, label2) の形式で、「一」側から「多」側にラベルをコピーできる:

# メタデータのジョインでインスタンス情報を付加
rate(http_requests_total[5m])
  * on(instance) group_left(datacenter, team)
(instance_metadata)

この機能により、info メトリクス(メタデータを持つ値が常に1のメトリクス)とのジョインが可能になる。

6.3 Info メトリクスパターン

Kubernetes 環境でよく使われるパターン:

# kube_pod_info をジョインしてノード情報を付加
sum by (pod) (rate(container_cpu_usage_seconds_total[5m]))
  * on(pod) group_left(node)
kube_pod_info
# ビルド情報メトリクスの活用
up
  * on(instance, job) group_left(version)
prometheus_build_info

6.4 ベクトルマッチングの実践的なデバッグ

ベクトルマッチングの問題をデバッグする際のアプローチ:

# Step 1: 左辺と右辺を個別に評価し、ラベルを確認
rate(http_requests_total{status=~"5.."}[5m])
rate(http_requests_total[5m])

# Step 2: ラベルセットの違いを確認
# 左辺のラベル
group by (__name__, method, handler, status, instance, job) (
  rate(http_requests_total{status=~"5.."}[5m])
)

# Step 3: マッチしない要素がないか確認
# 左辺にあって右辺にないもの
rate(http_requests_total{status=~"5.."}[5m])
  unless on(method, handler, instance, job)
rate(http_requests_total[5m])

6.5 よくある落とし穴

1. ラベルの不一致

# 問題: job ラベルが異なるため結果が空
node_memory_MemTotal_bytes{job="node-exporter"}
  -
node_memory_MemAvailable_bytes{job="node_exporter"}  # ハイフン vs アンダースコア

# 対策: on() で明示的にマッチングラベルを指定
node_memory_MemTotal_bytes
  - on(instance)
node_memory_MemAvailable_bytes

2. 多対多のエラー

# エラー: "many-to-many matching not allowed"
http_requests_total{status=~"5.."}
  /
http_requests_total

# 対策: 片方を集約するか、group_left/group_right を使用
sum by (method, handler) (rate(http_requests_total{status=~"5.."}[5m]))
  /
sum by (method, handler) (rate(http_requests_total[5m]))

3. ラベルの競合

# エラー: group_left でコピーしたラベルが左辺に既に存在する場合
rate(http_requests_total[5m])
  * on(instance) group_left(job)  # job は左辺にも存在 → エラー
instance_info

6.6 ベクトルマッチングのまとめ図

一対一(デフォルト):
  Left:  {a="1", b="2"} ←→ Right: {a="1", b="2"}
  Left:  {a="1", b="3"} ←→ Right: {a="1", b="3"}

ignoring(b):
  Left:  {a="1", b="2"} ←?→ Right: {a="1", b="X"}
  Left:  {a="1", b="3"} ←?→ Right: {a="1", b="Y"}
  → 右辺に a="1" が1つしかなければ OK
  → 右辺に a="1" が複数あれば group_left/group_right が必要

on(a):
  Left:  {a="1", ...} ←→ Right: {a="1", ...}
  a のみでマッチング(他のラベルは無視)

7. サブクエリ(Subqueries)

7.1 サブクエリとは

サブクエリは、インスタントベクトルを返す式に対してレンジベクトルのように時間範囲を指定し、その範囲内で式を繰り返し評価する機能である。Prometheus 2.7 で導入された。

構文

<instant_query>[<range>:<resolution>]
  • <range>: 評価する時間範囲
  • <resolution>: 評価のステップ間隔(省略するとグローバルの evaluation interval が使用される)

7.2 基本的な使用例

# 過去1時間の5分間レートの最大値(1分間隔で評価)
max_over_time(rate(http_requests_total[5m])[1h:1m])

# 過去30分間の5分間レートの平均値
avg_over_time(rate(http_requests_total[5m])[30m:1m])

# 過去24時間の1時間増加量の95パーセンタイル
quantile_over_time(0.95, increase(http_requests_total[1h])[24h:1h])

7.3 サブクエリ vs Recording Rules

サブクエリの機能は Recording Rules でも実現できるが、トレードオフがある:

観点サブクエリRecording Rules
設定クエリ内で完結別途ルールファイルが必要
パフォーマンスクエリ時に計算事前計算済み
柔軟性アドホックに変更可能ルール変更後のデータは遡及しない
リソース消費重い場合があるストレージを追加消費

推奨:

  • ダッシュボードやアラートで頻繁に使うクエリ → Recording Rules
  • 一時的な調査やアドホック分析 → サブクエリ

7.4 サブクエリの実用例

# CPUスパイクの検出:5分間のCPU使用率が過去1時間の平均から2標準偏差以上
(
  avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))
  <
  avg_over_time(
    avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))
  [1h:5m])
  - 2 * stddev_over_time(
    avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))
  [1h:5m])
)

# 過去6時間で最もリクエストが多かった5分間のレート
max_over_time(sum(rate(http_requests_total[5m]))[6h:1m])

# リクエストレートが過去1時間で一度でも100を超えたかどうか
max_over_time(sum(rate(http_requests_total[5m]))[1h:1m]) > 100

7.5 ネストされたサブクエリ

サブクエリはネスト可能だが、パフォーマンスに注意が必要:

# 過去24時間の1時間移動平均の最大値
max_over_time(
  avg_over_time(
    rate(http_requests_total[5m])
  [1h:5m])
[24h:1h])

8. Recording Rules

8.1 Recording Rules の概要

Recording Rules は、よく使う PromQL 式を事前に計算し、新しい時系列として保存する仕組みである。これにより:

  • パフォーマンス向上: 複雑なクエリの結果を事前計算
  • ダッシュボードの高速化: グラフの描画時間を短縮
  • クエリの簡素化: 複雑な式を短いメトリクス名で参照可能

8.2 設定方法

Prometheus の設定ファイル(prometheus.yml)でルールファイルを指定:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s  # Recording Rules の評価間隔

rule_files:
  - "rules/recording_rules.yml"
  - "rules/alerting_rules.yml"

8.3 Recording Rules の記述

# rules/recording_rules.yml
groups:
  - name: http_rules
    interval: 15s  # グループ固有の評価間隔(省略するとglobalを使用)
    rules:
      # 5分間のHTTPリクエストレート(ジョブ・ハンドラー・メソッド別)
      - record: job_handler_method:http_requests:rate5m
        expr: sum by (job, handler, method) (rate(http_requests_total[5m]))

      # HTTPエラーレート
      - record: job_handler:http_errors:rate5m
        expr: sum by (job, handler) (rate(http_requests_total{status=~"5.."}[5m]))

      # HTTPエラー率(%)
      - record: job_handler:http_error_rate:ratio_rate5m
        expr: |
          job_handler:http_errors:rate5m
            / on(job, handler) 
          sum by (job, handler) (rate(http_requests_total[5m]))

  - name: node_rules
    rules:
      # CPU使用率(インスタンス別)
      - record: instance:node_cpu_utilisation:rate5m
        expr: |
          1 - avg by (instance) (
            rate(node_cpu_seconds_total{mode="idle"}[5m])
          )

      # メモリ使用率(インスタンス別)
      - record: instance:node_memory_utilisation:ratio
        expr: |
          1 - (
            node_memory_MemAvailable_bytes
              / node_memory_MemTotal_bytes
          )

      # ディスク使用率(インスタンス・デバイス別)
      - record: instance_device:node_filesystem_utilisation:ratio
        expr: |
          (node_filesystem_size_bytes - node_filesystem_avail_bytes)
            / node_filesystem_size_bytes

      # ネットワーク受信スループット
      - record: instance:node_network_receive_bytes:rate5m
        expr: |
          sum by (instance) (
            rate(node_network_receive_bytes_total[5m])
          )

8.4 命名規則

Recording Rules のメトリクス名には以下の慣例がある:

level:metric:operations
  • level: 集約レベル(job, instance, cluster など)
  • metric: 元のメトリクス名(サフィックスを除く)
  • operations: 適用された操作(rate5m, ratio, count など)

例:

  • job:http_requests:rate5m — ジョブ別の5分間レート
  • instance:node_cpu:ratio — インスタンス別のCPU比率
  • cluster_namespace_pod:memory_usage:sum — クラスタ・NS・Pod別のメモリ合計

8.5 Recording Rules のベストプラクティス

groups:
  - name: latency_rules
    rules:
      # 1. rate() は Recording Rule で事前計算する
      - record: job_handler:http_request_duration_seconds_bucket:rate5m
        expr: sum by (job, handler, le) (rate(http_request_duration_seconds_bucket[5m]))

      # 2. histogram_quantile は事前計算済みの rate の上で実行
      - record: job_handler:http_request_duration_seconds:p99_rate5m
        expr: histogram_quantile(0.99, job_handler:http_request_duration_seconds_bucket:rate5m)

      - record: job_handler:http_request_duration_seconds:p95_rate5m
        expr: histogram_quantile(0.95, job_handler:http_request_duration_seconds_bucket:rate5m)

      - record: job_handler:http_request_duration_seconds:p50_rate5m
        expr: histogram_quantile(0.50, job_handler:http_request_duration_seconds_bucket:rate5m)

      # 3. 平均レイテンシ
      - record: job_handler:http_request_duration_seconds:avg_rate5m
        expr: |
          sum by (job, handler) (rate(http_request_duration_seconds_sum[5m]))
            /
          sum by (job, handler) (rate(http_request_duration_seconds_count[5m]))

8.6 Recording Rules の検証

# ルールファイルの構文チェック
promtool check rules rules/recording_rules.yml

# 出力例
# Checking rules/recording_rules.yml
#   SUCCESS: 8 rules found

# ユニットテスト
promtool test rules tests/rules_test.yml

テストファイルの例:

# tests/rules_test.yml
rule_files:
  - rules/recording_rules.yml

evaluation_interval: 1m

tests:
  - interval: 1m
    input_series:
      - series: 'http_requests_total{job="api", handler="/users", method="GET", status="200"}'
        values: "0+10x10"  # 0から10ずつ増加、10サンプル
      - series: 'http_requests_total{job="api", handler="/users", method="GET", status="500"}'
        values: "0+1x10"   # 0から1ずつ増加、10サンプル

    promql_expr_test:
      - expr: job_handler:http_error_rate:ratio_rate5m{job="api", handler="/users"}
        eval_time: 10m
        exp_samples:
          - labels: 'job_handler:http_error_rate:ratio_rate5m{job="api", handler="/users"}'
            value: 0.09090909090909091  # ≈ 1/11

9. Alerting Rules

9.1 Alerting Rules の概要

Alerting Rules は、PromQL の式が特定の条件を満たした場合にアラートを発火させる仕組みである。Prometheus はアラートの状態を管理し、Alertmanager に通知を送信する。

9.2 アラートの状態遷移

Inactive → Pending → Firing → Resolved (Inactive)
  • Inactive: 条件を満たしていない
  • Pending: 条件を満たしているが、for で指定した待機時間が経過していない
  • Firing: 条件を満たし、待機時間も経過。Alertmanager に通知される
  • Resolved: 条件を満たさなくなった。Alertmanager に解決通知が送られる

9.3 Alerting Rules の記述

# rules/alerting_rules.yml
groups:
  - name: instance_alerts
    rules:
      # ターゲットダウンの検出
      - alert: InstanceDown
        expr: up == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Instance {{ $labels.instance }} down"
          description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes."
          runbook_url: "https://wiki.example.com/runbook/instance-down"

      # 高CPU使用率
      - alert: HighCPUUsage
        expr: |
          instance:node_cpu_utilisation:rate5m > 0.9
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage on {{ $labels.instance }}"
          description: "CPU usage is {{ printf \"%.1f\" (mul $value 100) }}% on {{ $labels.instance }}."

      # メモリ不足
      - alert: LowMemory
        expr: |
          instance:node_memory_utilisation:ratio > 0.9
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Low available memory on {{ $labels.instance }}"
          description: "Memory usage is {{ printf \"%.1f\" (mul $value 100) }}% on {{ $labels.instance }}."

  - name: http_alerts
    rules:
      # 高エラーレート
      - alert: HighHTTPErrorRate
        expr: |
          (
            sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
              /
            sum by (job) (rate(http_requests_total[5m]))
          ) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High HTTP error rate for {{ $labels.job }}"
          description: "HTTP error rate is {{ printf \"%.2f\" (mul $value 100) }}% for {{ $labels.job }}."

      # レイテンシ劣化
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99, 
            sum by (job, le) (rate(http_request_duration_seconds_bucket[5m]))
          ) > 1.0
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High latency for {{ $labels.job }}"
          description: "99th percentile latency is {{ printf \"%.3f\" $value }}s for {{ $labels.job }}."

  - name: disk_alerts
    rules:
      # ディスク枯渇予測
      - alert: DiskWillFillIn24h
        expr: |
          predict_linear(
            node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.*"}[6h], 
            24*3600
          ) < 0
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "Disk space will be exhausted within 24h on {{ $labels.instance }}"
          description: "Filesystem {{ $labels.mountpoint }} on {{ $labels.instance }} is predicted to run out of space within 24 hours."

      # ディスク使用率90%超
      - alert: DiskUsageHigh
        expr: |
          (node_filesystem_size_bytes - node_filesystem_avail_bytes)
            / node_filesystem_size_bytes > 0.9
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Disk usage above 90% on {{ $labels.instance }}"
          description: "Filesystem {{ $labels.mountpoint }} usage is {{ printf \"%.1f\" (mul $value 100) }}%."

9.4 for 句の設計

for は誤報(false positive)を減らすために重要:

# 短い for(1-5分): 即時性が重要なアラート
- alert: InstanceDown
  expr: up == 0
  for: 3m  # スクレイプ失敗が3分続いたら通知

# 中程度の for(5-15分): 一般的なパフォーマンスアラート
- alert: HighErrorRate
  expr: error_rate > 0.05
  for: 10m  # 一時的なスパイクを除外

# 長い for(15-60分): トレンドベースのアラート
- alert: DiskWillFillIn24h
  expr: predict_linear(disk_free[6h], 24*3600) < 0
  for: 30m  # 予測の信頼性を高める

9.5 テンプレート変数

アノテーションで使用できるテンプレート変数:

annotations:
  # 現在の値を参照
  description: "Value: {{ $value }}"
  
  # ラベルを参照
  description: "Instance: {{ $labels.instance }}, Job: {{ $labels.job }}"
  
  # フォーマット
  description: "Usage: {{ printf \"%.2f\" $value }}%"
  
  # 計算
  description: "Usage: {{ printf \"%.1f\" (mul $value 100) }}%"
  
  # 条件付き
  description: >-
    {{ if gt $value 0.9 }}Critical{{ else }}Warning{{ end }}: 
    CPU at {{ printf "%.0f" (mul $value 100) }}%

9.6 Alertmanager との連携

# alertmanager.yml(Alertmanager 設定)
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/...'

route:
  receiver: 'default'
  group_by: ['alertname', 'job']
  group_wait: 30s       # 最初のアラートから通知までの待機
  group_interval: 5m    # 同じグループの再通知間隔
  repeat_interval: 4h   # 同じアラートの繰り返し通知間隔
  
  routes:
    - match:
        severity: critical
      receiver: 'pagerduty-critical'
      group_wait: 10s
    
    - match:
        severity: warning
      receiver: 'slack-warnings'

receivers:
  - name: 'default'
    slack_configs:
      - channel: '#alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *{{ .Annotations.summary }}*
          {{ .Annotations.description }}
          {{ end }}

  - name: 'pagerduty-critical'
    pagerduty_configs:
      - service_key: '<PagerDuty-Service-Key>'
        severity: critical
        description: '{{ .GroupLabels.alertname }}: {{ .CommonAnnotations.summary }}'

  - name: 'slack-warnings'
    slack_configs:
      - channel: '#alerts-warnings'
        send_resolved: true

# 特定のアラートを抑制
inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'instance']

9.7 マルチバーンレートアラート(SLO ベース)

Google の SRE ブックで提唱されたマルチウィンドウ・マルチバーンレートアラート:

groups:
  - name: slo_rules
    rules:
      # エラーバジェット消費率の Recording Rules
      - record: job:slo_errors_per_request:ratio_rate5m
        expr: sum by (job) (rate(http_requests_total{status=~"5.."}[5m])) / sum by (job) (rate(http_requests_total[5m]))

      - record: job:slo_errors_per_request:ratio_rate30m
        expr: sum by (job) (rate(http_requests_total{status=~"5.."}[30m])) / sum by (job) (rate(http_requests_total[30m]))

      - record: job:slo_errors_per_request:ratio_rate1h
        expr: sum by (job) (rate(http_requests_total{status=~"5.."}[1h])) / sum by (job) (rate(http_requests_total[1h]))

      - record: job:slo_errors_per_request:ratio_rate6h
        expr: sum by (job) (rate(http_requests_total{status=~"5.."}[6h])) / sum by (job) (rate(http_requests_total[6h]))

  - name: slo_alerts
    rules:
      # SLO 99.9% (エラーバジェット 0.1%)
      # 高速バーン: 5分/30分ウィンドウ、14.4xバーンレート
      - alert: ErrorBudgetFastBurn
        expr: |
          job:slo_errors_per_request:ratio_rate5m > (14.4 * 0.001)
            and
          job:slo_errors_per_request:ratio_rate30m > (14.4 * 0.001)
        for: 2m
        labels:
          severity: critical
          slo: "99.9"
        annotations:
          summary: "Fast error budget burn for {{ $labels.job }}"

      # 低速バーン: 1時間/6時間ウィンドウ、6xバーンレート
      - alert: ErrorBudgetSlowBurn
        expr: |
          job:slo_errors_per_request:ratio_rate1h > (6 * 0.001)
            and
          job:slo_errors_per_request:ratio_rate6h > (6 * 0.001)
        for: 15m
        labels:
          severity: warning
          slo: "99.9"
        annotations:
          summary: "Slow error budget burn for {{ $labels.job }}"

10. Histogram と Summary の詳細

10.1 Histogram の仕組み

Histogram メトリクスは、観測値の分布を把握するために使用される。クライアントライブラリが観測値を設定済みのバケット(bucket)に分類し、以下の3種類の時系列を生成する:

# バケット(累積カウント)
http_request_duration_seconds_bucket{handler="/api/users", le="0.005"} 24054
http_request_duration_seconds_bucket{handler="/api/users", le="0.01"} 33444
http_request_duration_seconds_bucket{handler="/api/users", le="0.025"} 100392
http_request_duration_seconds_bucket{handler="/api/users", le="0.05"} 129389
http_request_duration_seconds_bucket{handler="/api/users", le="0.1"} 133988
http_request_duration_seconds_bucket{handler="/api/users", le="0.25"} 144320
http_request_duration_seconds_bucket{handler="/api/users", le="0.5"} 144700
http_request_duration_seconds_bucket{handler="/api/users", le="1"} 144838
http_request_duration_seconds_bucket{handler="/api/users", le="2.5"} 144900
http_request_duration_seconds_bucket{handler="/api/users", le="5"} 144924
http_request_duration_seconds_bucket{handler="/api/users", le="10"} 144930
http_request_duration_seconds_bucket{handler="/api/users", le="+Inf"} 144930

# 合計値
http_request_duration_seconds_sum{handler="/api/users"} 53423.34

# カウント
http_request_duration_seconds_count{handler="/api/users"} 144930

重要: バケットは累積的である。le="0.1" のバケットには 0.1 秒以下のすべてのリクエストが含まれる。

10.2 histogram_quantile() 関数

バケットデータから分位数を線形補間で推定する。

# 基本的な使い方: 99パーセンタイルレイテンシ
histogram_quantile(0.99, 
  rate(http_request_duration_seconds_bucket[5m])
)

重要: histogram_quantile() に渡すバケットデータには必ず rate() を適用すること。生のバケットカウンターを渡すと、サーバ再起動時のカウンターリセットで誤った結果になる。

ラベル別のパーセンタイル

# ハンドラー別の99パーセンタイル
histogram_quantile(0.99, 
  sum by (handler, le) (rate(http_request_duration_seconds_bucket[5m]))
)

# ジョブ全体の95パーセンタイル
histogram_quantile(0.95, 
  sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)

le ラベルは必ず保持すること。le を集約で除外すると正しく動作しない。

10.3 平均値とスループットの計算

# 平均リクエスト時間
rate(http_request_duration_seconds_sum[5m])
  / rate(http_request_duration_seconds_count[5m])

# スループット(リクエスト/秒)
rate(http_request_duration_seconds_count[5m])

10.4 Histogram のバケット設計

デフォルトのバケット

Go クライアントのデフォルト:

{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}

カスタムバケット

// Go でのカスタムバケット定義
httpDuration := prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "http_request_duration_seconds",
        Help:    "Duration of HTTP requests.",
        Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5},
    },
    []string{"handler", "method"},
)
# Python でのカスタムバケット定義
from prometheus_client import Histogram

REQUEST_DURATION = Histogram(
    'http_request_duration_seconds',
    'Duration of HTTP requests',
    ['handler', 'method'],
    buckets=[0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5]
)

バケット設計の指針

  1. SLO に基づく: SLO が「99% のリクエストを 500ms 以内」なら、500ms 付近に細かいバケットを配置
  2. 対数的分布: レイテンシは通常対数正規分布に従うため、等比的なバケットが適切
  3. 十分な粒度: 最低 8〜10 バケットを推奨
  4. 上限: 最大バケットは実際に起こりうる最大値を超えること
// SLO ベースのバケット設計例
// SLO: p99 < 1s, p95 < 500ms, p50 < 100ms
Buckets: []float64{
    0.01,   // 10ms
    0.025,  // 25ms
    0.05,   // 50ms
    0.1,    // 100ms (p50 SLO)
    0.25,   // 250ms
    0.5,    // 500ms (p95 SLO)
    1.0,    // 1s (p99 SLO)
    2.5,    // 2.5s
    5.0,    // 5s
    10.0,   // 10s
}

10.5 Summary との比較

特性HistogramSummary
分位数計算サーバサイド(PromQL)クライアントサイド
集約可能(バケットを sum できる)不可能(分位数は集約不可)
精度バケット粒度に依存設定した分位数で高精度
コストバケット数 × カーディナリティ固定(設定した分位数の数)
動的なφ任意のφで事後計算可能事前に設定したφのみ
推奨度ほとんどの場合で推奨特殊なユースケースのみ

10.6 Summary メトリクスの使用

# Summary メトリクスの形式
rpc_duration_seconds{quantile="0.5"} 0.000413
rpc_duration_seconds{quantile="0.9"} 0.000725
rpc_duration_seconds{quantile="0.99"} 0.001259
rpc_duration_seconds_sum 17.242
rpc_duration_seconds_count 11859
# Summary の分位数を直接使用
rpc_duration_seconds{quantile="0.99"}

# Summary でも平均は計算可能
rate(rpc_duration_seconds_sum[5m]) / rate(rpc_duration_seconds_count[5m])

注意: Summary の分位数(quantile ラベル)は集約できない。複数インスタンスの分位数を avgsum で集約することは統計的に無意味。

10.7 Native Histograms(Prometheus 2.40+)

Native Histograms(旧称: Sparse Histograms)は、Prometheus 2.40 で実験的に導入された新しいヒストグラム形式:

# Native Histogram からの分位数計算
histogram_quantile(0.99, http_request_duration_seconds)

# バケット数の自動調整により、手動でのバケット設計が不要
# カーディナリティの大幅な削減

特徴:

  • バケットの自動スケーリング
  • 固定バケットに比べて時系列数が大幅に削減
  • より正確な分位数推定
  • --enable-feature=native-histograms で有効化
# prometheus.yml での Native Histogram 有効化
global:
  scrape_interval: 15s
  scrape_protocols:
    - PrometheusProto    # Native Histogram を含むプロトコル
    - OpenMetricsText1.0.0
    - OpenMetricsText0.0.1
    - PrometheusText0.0.4

10.8 Histogram の実践パターン

# Apdex スコアの計算
# (satisfied + tolerating/2) / total
# satisfied: 0.3s以下, tolerating: 1.2s以下
(
  sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m]))
  +
  sum(rate(http_request_duration_seconds_bucket{le="1.2"}[5m]))
) / 2 / sum(rate(http_request_duration_seconds_count[5m]))

# SLI: リクエストの99%が500ms以下で完了しているか
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
  / sum(rate(http_request_duration_seconds_count[5m])) >= 0.99

# レイテンシの時間変化を複数パーセンタイルで可視化
# Grafana で複数クエリとして設定
# Query A: P99
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# Query B: P95
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# Query C: P50
histogram_quantile(0.50, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

11. 実践的なクエリパターン

11.1 RED メソッド(Rate, Errors, Duration)

マイクロサービスのモニタリングで広く使われるメソッド:

# Rate(リクエストレート)
sum by (service) (rate(http_requests_total[5m]))

# Errors(エラーレート)
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
  / sum by (service) (rate(http_requests_total[5m]))

# Duration(レイテンシ)
histogram_quantile(0.99, 
  sum by (service, le) (rate(http_request_duration_seconds_bucket[5m]))
)

11.2 USE メソッド(Utilization, Saturation, Errors)

インフラストラクチャのモニタリング:

# === CPU ===
# Utilization(使用率)
1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

# Saturation(飽和度 — ロードアベレージ / CPU数)
node_load1 / count by (instance) (node_cpu_seconds_total{mode="idle"})

# Errors
rate(node_cpu_guest_seconds_total[5m])  # CPUエラーは稀

# === Memory ===
# Utilization
1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes

# Saturation(スワップ使用率)
(node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes) 
  / node_memory_SwapTotal_bytes

# === Disk I/O ===
# Utilization(ディスクビジー率)
rate(node_disk_io_time_seconds_total[5m])

# Saturation(I/Oキューの長さ)
rate(node_disk_io_time_weighted_seconds_total[5m])

# === Network ===
# Utilization(帯域使用率、1Gbps NIC の場合)
rate(node_network_receive_bytes_total[5m]) * 8 / 1e9

# Saturation(ドロップパケット)
rate(node_network_receive_drop_total[5m])

# Errors
rate(node_network_receive_errs_total[5m])

11.3 Kubernetes モニタリングパターン

# === Pod リソース使用率 ===
# CPU使用率(リクエスト比)
sum by (namespace, pod) (rate(container_cpu_usage_seconds_total{container!=""}[5m]))
  / sum by (namespace, pod) (kube_pod_container_resource_requests{resource="cpu"})

# メモリ使用率(リミット比)
sum by (namespace, pod) (container_memory_working_set_bytes{container!=""})
  / sum by (namespace, pod) (kube_pod_container_resource_limits{resource="memory"})

# === Deployment の状態 ===
# 利用可能でないレプリカ
kube_deployment_spec_replicas - kube_deployment_status_replicas_available

# Deployment のロールアウト進行中
kube_deployment_status_observed_generation != kube_deployment_metadata_generation

# === Pod の異常状態 ===
# CrashLoopBackOff の Pod
increase(kube_pod_container_status_restarts_total[1h]) > 5

# Pending 状態が長い Pod
kube_pod_status_phase{phase="Pending"} == 1
  and on(pod, namespace)
(time() - kube_pod_created > 300)

# OOMKilled の検出
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1

# === HPA(水平Pod自動スケーリング)===
# HPAが最大にスケールアウト
kube_horizontalpodautoscaler_status_current_replicas
  == kube_horizontalpodautoscaler_spec_max_replicas

# HPAのターゲット使用率との乖離
kube_horizontalpodautoscaler_status_current_replicas
  / kube_horizontalpodautoscaler_spec_max_replicas > 0.8

# === Node の状態 ===
# NotReady なノード
kube_node_status_condition{condition="Ready", status="true"} == 0

# ノードのリソース圧迫
kube_node_status_condition{condition="MemoryPressure", status="true"} == 1
kube_node_status_condition{condition="DiskPressure", status="true"} == 1
kube_node_status_condition{condition="PIDPressure", status="true"} == 1

11.4 SLI/SLO パターン

# === 可用性 SLI ===
# 成功リクエスト率(30日ウィンドウ)
sum(increase(http_requests_total{status!~"5.."}[30d]))
  / sum(increase(http_requests_total[30d]))

# エラーバジェット残量(SLO 99.9%)
1 - (
  (1 - (
    sum(increase(http_requests_total{status!~"5.."}[30d]))
      / sum(increase(http_requests_total[30d]))
  )) / (1 - 0.999)
)

# === レイテンシ SLI ===
# SLOを満たすリクエストの割合(500ms以下で応答した割合)
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
  / sum(rate(http_request_duration_seconds_count[5m]))

# === スループット SLI ===
# リクエストの処理能力が閾値以上を維持している時間の割合
avg_over_time(
  (sum(rate(http_requests_total[5m])) > bool 100)[30d:5m]
)

11.5 アプリケーション固有のパターン

# === データベース ===
# PostgreSQL 接続使用率
pg_stat_activity_count / pg_settings_max_connections

# MySQL スロークエリレート
rate(mysql_global_status_slow_queries[5m])

# Redis ヒット率
rate(redis_keyspace_hits_total[5m]) 
  / (rate(redis_keyspace_hits_total[5m]) + rate(redis_keyspace_misses_total[5m]))

# === メッセージキュー ===
# Kafka コンシューマーラグ
kafka_consumergroup_lag_sum

# RabbitMQ キューの深さ
rabbitmq_queue_messages

# === gRPC ===
# gRPC エラーレート
sum by (grpc_service, grpc_method) (
  rate(grpc_server_handled_total{grpc_code!="OK"}[5m])
) / sum by (grpc_service, grpc_method) (
  rate(grpc_server_handled_total[5m])
)

# === JVM ===
# GC暫停時間
rate(jvm_gc_pause_seconds_sum[5m])

# ヒープ使用率
jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}

11.6 変化率と異常検知パターン

# 前日同時刻との比較(日次パターンの考慮)
rate(http_requests_total[5m]) 
  / rate(http_requests_total[5m] offset 1d) - 1

# 前週同曜日との比較
rate(http_requests_total[1h])
  / rate(http_requests_total[1h] offset 7d)

# Zスコアベースの異常検知(過去1時間の平均と標準偏差に基づく)
(
  rate(http_requests_total[5m])
  - avg_over_time(rate(http_requests_total[5m])[1h:5m])
) / stddev_over_time(rate(http_requests_total[5m])[1h:5m])

# 急激な変化の検出(5分間で50%以上の変化)
abs(
  rate(http_requests_total[5m])
  - rate(http_requests_total[5m] offset 5m)
) / rate(http_requests_total[5m] offset 5m) > 0.5

# トラフィックの突然の消失
rate(http_requests_total[5m]) == 0
  and
rate(http_requests_total[5m] offset 10m) > 0

11.7 コスト関連のクエリ

# === Prometheus 自身のリソース消費 ===
# 時系列数(カーディナリティ)
prometheus_tsdb_head_series

# サンプル取り込みレート
rate(prometheus_tsdb_head_samples_appended_total[5m])

# メモリ使用量
process_resident_memory_bytes{job="prometheus"}

# ストレージサイズ
prometheus_tsdb_storage_blocks_bytes

# スクレイプ時間
scrape_duration_seconds

# 高カーディナリティなメトリクスの発見
topk(10, count by (__name__) ({__name__!=""}))

# ジョブ別の時系列数
count by (job) ({__name__!=""})

12. パフォーマンスとベストプラクティス

12.1 クエリパフォーマンスの基礎

PromQL クエリのパフォーマンスは主に以下の要因に左右される:

  1. マッチする時系列の数(カーディナリティ): 最も重要な要因
  2. 時間範囲の長さ: 長い範囲ほど多くのサンプルを読み込む
  3. クエリの複雑さ: ネストされた関数や集約の数
  4. Resolution(ステップ間隔): レンジクエリの評価ポイント数

12.2 カーディナリティの管理

カーディナリティ(一意の時系列の数)は Prometheus のパフォーマンスに直結する。

# 現在のアクティブ時系列数
prometheus_tsdb_head_series

# メトリクス名別の時系列数(上位20)
topk(20, count by (__name__) ({__name__!=""}))

# ジョブ別の時系列数
sort_desc(count by (job) ({__name__!=""}))

# 特定のメトリクスのラベルカーディナリティ
count(http_requests_total) by (handler)

カーディナリティ爆発の原因と対策

原因対策
高カーディナリティラベルuser_id, request_idラベルとして使わずログに記録
無制限のラベル値path="/users/12345"パスのパラメータ化 (/users/:id)
ラベルの組み合わせ爆発method × path × status × instance不要なラベルの削除
不要なメトリクス使用していないメトリクスmetric_relabel_configs で除外
# prometheus.yml でのメトリクス除外
scrape_configs:
  - job_name: 'app'
    metric_relabel_configs:
      # 不要なメトリクスの除外
      - source_labels: [__name__]
        regex: "go_.*"
        action: drop
      
      # 高カーディナリティラベルの除外
      - regex: "request_id"
        action: labeldrop
      
      # パスの正規化
      - source_labels: [path]
        regex: "/users/[0-9]+"
        target_label: path
        replacement: "/users/:id"

12.3 効率的なクエリの書き方

1. ラベルマッチャーでの早期フィルタリング

# 悪い例: 全メトリクスを取得してから集約
sum(rate(http_requests_total[5m])) by (handler)

# 良い例: 必要なラベルで事前にフィルタリング
sum by (handler) (rate(http_requests_total{job="api-server"}[5m]))

2. Recording Rules の活用

# 悪い例: ダッシュボードで毎回重い計算
histogram_quantile(0.99, 
  sum by (handler, le) (rate(http_request_duration_seconds_bucket[5m]))
)

# 良い例: Recording Rule で事前計算
# recording_rules.yml で定義済みの handler:http_request_duration_seconds:p99 を使用
handler:http_request_duration_seconds:p99

3. 不要な {__name__=~".*"} を避ける

# 悪い例: すべてのメトリクスにマッチ(パフォーマンスが非常に悪い)
{__name__=~".+"}

# 良い例: 具体的なメトリクス名を指定
http_requests_total

4. 正規表現の最適化

# 悪い例: 非効率な正規表現
{__name__=~".*requests.*"}

# 良い例: プレフィックスで効率化
{__name__=~"http_requests.*"}

# さらに良い例: 具体的なメトリクス名
http_requests_total

5. rate() の前に集約しない

# 間違い: Counter をまず合計してから rate を取る
# (Counter のリセットが正しく検出されない)
rate(sum by (job) (http_requests_total)[5m])

# 正しい: rate を先に計算してから集約
sum by (job) (rate(http_requests_total[5m]))

12.4 Grafana ダッシュボードでの最適化

変数の活用

# Grafanaのテンプレート変数を使用
rate(http_requests_total{job="$job", instance=~"$instance"}[5m])

$__rate_interval の使用

Grafana 7.2+ では $__rate_interval 変数が使用可能。これはスクレイプ間隔とダッシュボードの間隔を考慮した最適な範囲を自動計算する。

# Grafana での推奨
rate(http_requests_total[$__rate_interval])

# 従来の固定値(非推奨)
rate(http_requests_total[5m])

$__rate_interval は以下のように計算される:

max($__interval + scrape_interval, 4 * scrape_interval)

最大データポイント数の制限

Grafana パネルの「Max data points」設定により、不要に細かい解像度での評価を防ぐ。

12.5 よくある間違いと修正

1. Gauge に rate() を使用

# 間違い: Gauge メトリクスに rate
rate(node_memory_MemAvailable_bytes[5m])

# 正しい: Gauge の変化率には deriv() か delta()
deriv(node_memory_MemAvailable_bytes[1h])
delta(node_memory_MemAvailable_bytes[1h])

2. Counter に delta() を使用

# 間違い: Counter に delta(リセットが考慮されない)
delta(http_requests_total[1h])

# 正しい: Counter には increase()
increase(http_requests_total[1h])

3. 範囲が短すぎる rate()

# 問題: scrape_interval=15s で 15s の範囲
# サンプルが1つしかない可能性があり、rate が計算できない
rate(http_requests_total[15s])

# 修正: 最低でも scrape_interval の 4 倍
rate(http_requests_total[1m])

4. histogram_quantile に生のカウンターを渡す

# 間違い: rate を適用していない
histogram_quantile(0.99, http_request_duration_seconds_bucket)

# 正しい: rate を適用
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

5. le ラベルの除外

# 間違い: le ラベルが除外されている
histogram_quantile(0.99, 
  sum by (handler) (rate(http_request_duration_seconds_bucket[5m]))
)

# 正しい: le を保持
histogram_quantile(0.99, 
  sum by (handler, le) (rate(http_request_duration_seconds_bucket[5m]))
)

12.6 クエリの実行計画とデバッグ

Prometheus の /api/v1/query エンドポイントでクエリの実行時間を確認:

# クエリの実行時間を確認
curl -s 'http://prometheus:9090/api/v1/query' \
  --data-urlencode 'query=sum by (handler) (rate(http_requests_total[5m]))' \
  | jq '.data.stats'

# クエリログの有効化(prometheus.yml)
# --query.log-file=/var/log/prometheus/query.log

Prometheus 2.43+ では --enable-feature=promql-experimental-functionsinfo() 関数が使用可能。

13. Prometheus のアーキテクチャと PromQL の実行

13.1 Prometheus の全体アーキテクチャ

┌──────────────────────────────────────────────────────────────┐
│                      Prometheus Server                        │
│                                                               │
│  ┌─────────────┐   ┌──────────────┐   ┌──────────────────┐  │
│  │  Retrieval   │   │   TSDB       │   │  HTTP Server     │  │
│  │  (Scraper)   │──▶│  (Storage)   │◀──│  (PromQL API)    │  │
│  └─────────────┘   └──────────────┘   └──────────────────┘  │
│        ▲                   │                    ▲             │
│        │                   ▼                    │             │
│  ┌─────────────┐   ┌──────────────┐   ┌──────────────────┐  │
│  │  Service     │   │  Rule        │   │  Notifier        │  │
│  │  Discovery   │   │  Manager     │   │  (Alertmanager)  │  │
│  └─────────────┘   └──────────────┘   └──────────────────┘  │
└──────────────────────────────────────────────────────────────┘
         ▲                                        │
         │                                        ▼
┌─────────────────┐                    ┌──────────────────┐
│  Targets         │                    │  Alertmanager     │
│  (Exporters,     │                    │                   │
│   Applications)  │                    │  → Slack          │
└─────────────────┘                    │  → PagerDuty      │
                                        │  → Email          │
                                        └──────────────────┘

13.2 TSDB(Time Series Database)の内部構造

Prometheus の TSDB は以下のコンポーネントで構成される:

Head Block(インメモリ)

  • 直近のデータ(通常 2 時間分)をメモリ上に保持
  • Write-Ahead Log(WAL)で永続化
  • 書き込みと読み取りの両方を処理
data/
├── wal/           # Write-Ahead Log
│   ├── 00000001
│   ├── 00000002
│   └── checkpoint.000001/
├── chunks_head/   # メモリマップドチャンク
└── ...

Persistent Blocks(ディスク)

  • 2 時間ごとに Head Block からコンパクションされる
  • 不変(immutable)のブロックとしてディスクに書き込み
  • さらに古いブロック同士がマージ(コンパクション)される
data/
├── 01BKGV7JBM69T2G1BGBGM6KB12/  # 永続ブロック
│   ├── meta.json                  # ブロックメタデータ
│   ├── chunks/                    # チャンクデータ
│   │   └── 000001
│   ├── index                      # インバーテッドインデックス
│   └── tombstones                 # 削除マーカー
├── 01BKGTZQ1SYQJTR4PB43C8PD98/
│   ├── meta.json
│   ├── chunks/
│   │   └── 000001
│   ├── index
│   └── tombstones
└── ...

インデックス構造

Prometheus は転置インデックス(inverted index)を使用してラベルから時系列を高速に検索する:

ラベル → 時系列のポスティングリスト

job="api"        → [series_1, series_2, series_3, ...]
method="GET"     → [series_1, series_4, series_5, ...]
status="200"     → [series_1, series_5, series_7, ...]

job="api" AND method="GET" → intersection([1,2,3], [1,4,5]) → [series_1]

13.3 PromQL エンジンの実行フロー

クエリ文字列
    │
    ▼
┌──────────┐
│  Parser   │  → AST(抽象構文木)の生成
└──────────┘
    │
    ▼
┌──────────────┐
│  Analyzer     │  → 型チェック、最適化
└──────────────┘
    │
    ▼
┌──────────────┐
│  Evaluator    │  → TSDB へのデータフェッチと計算
└──────────────┘
    │
    ▼
  結果

パース

rate(http_requests_total{method="GET"}[5m])

↓ AST:

Call{
  Func: "rate",
  Args: [
    MatrixSelector{
      VectorSelector{
        Name: "http_requests_total",
        Matchers: [
          {Type: "=", Name: "method", Value: "GET"}
        ]
      },
      Range: 5m
    }
  ]
}

評価

  1. セレクタの評価: TSDB のインデックスを検索し、マッチする時系列を特定
  2. データのフェッチ: 該当する時間範囲のサンプルをチャンクから読み込み
  3. 関数の適用: rate() などの関数を各時系列に適用
  4. 集約: sum, avg などの集約演算子を適用
  5. 結果の返却: JSON 形式で HTTP レスポンスとして返却

13.4 Instant Query vs Range Query

Instant Query(/api/v1/query

単一時点での評価:

curl 'http://prometheus:9090/api/v1/query' \
  --data-urlencode 'query=rate(http_requests_total[5m])' \
  --data-urlencode 'time=2024-01-01T00:00:00Z'
{
  "status": "success",
  "data": {
    "resultType": "vector",
    "result": [
      {
        "metric": {"method": "GET", "handler": "/api/users"},
        "value": [1704067200, "12.5"]
      }
    ]
  }
}

Range Query(/api/v1/query_range

時間範囲での評価(グラフ描画用):

curl 'http://prometheus:9090/api/v1/query_range' \
  --data-urlencode 'query=rate(http_requests_total[5m])' \
  --data-urlencode 'start=2024-01-01T00:00:00Z' \
  --data-urlencode 'end=2024-01-01T01:00:00Z' \
  --data-urlencode 'step=60s'
{
  "status": "success",
  "data": {
    "resultType": "matrix",
    "result": [
      {
        "metric": {"method": "GET", "handler": "/api/users"},
        "values": [
          [1704067200, "12.5"],
          [1704067260, "13.1"],
          [1704067320, "11.8"]
        ]
      }
    ]
  }
}

Range Query は、start から end まで step 間隔で Instant Query を繰り返し実行するのと概念的に等しい。ただし実装はバッチ最適化されている。

13.5 Staleness(失効)の仕組み

Prometheus 2.x では、スクレイプが失敗した場合やターゲットが消失した場合、5 分後にその時系列が「stale」とマークされる。

時間軸:
t0     t1     t2     t3     t4     t5     t6
│ OK   │ OK   │ FAIL │ FAIL │ FAIL │ stale│ (消失)
│      │      │      │      │      │      │
  • スクレイプ成功時にサンプルが記録される
  • スクレイプ失敗が続くと、5分後にstaleマーカーが挿入される
  • staleマーカー以降、その時系列はクエリ結果に含まれなくなる

これは lookback delta(デフォルト5分)に基づいており、--query.lookback-delta フラグで変更可能。

13.6 リモートストレージ

Prometheus はリモートストレージへの書き込みと読み取りをサポート:

# prometheus.yml
remote_write:
  - url: "http://thanos-receive:19291/api/v1/receive"
    queue_config:
      max_samples_per_send: 5000
      batch_send_deadline: 5s
      max_shards: 200

remote_read:
  - url: "http://thanos-query:9090/api/v1/read"
    read_recent: false  # 直近データはローカルTSDBから読み取る

13.7 設定例:Prometheus の完全な設定

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  scrape_timeout: 10s
  external_labels:
    cluster: "production"
    region: "us-west-2"

# Recording Rules と Alerting Rules
rule_files:
  - "rules/recording_rules.yml"
  - "rules/alerting_rules.yml"

# Alertmanager の設定
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - "alertmanager:9093"
      timeout: 10s

# スクレイプ設定
scrape_configs:
  # Prometheus 自身のモニタリング
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  # Node Exporter
  - job_name: "node"
    static_configs:
      - targets:
          - "server01:9100"
          - "server02:9100"
          - "server03:9100"
    relabel_configs:
      - source_labels: [__address__]
        regex: "(.*):.*"
        target_label: hostname
        replacement: "$1"

  # Kubernetes Pods(サービスディスカバリ)
  - job_name: "kubernetes-pods"
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # アノテーションでスクレイプ対象を制御
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
        target_label: __address__
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod

  # Blackbox Exporter(外形監視)
  - job_name: "blackbox-http"
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - "https://api.example.com/health"
          - "https://www.example.com"
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: "blackbox-exporter:9115"

# ストレージ設定
storage:
  tsdb:
    retention.time: 30d
    retention.size: 50GB

14. エコシステムと互換性

14.1 長期保存ソリューション

Prometheus 単体ではデフォルト 15 日のデータ保持期間であり、長期のデータ保存には専用のソリューションが必要。

Thanos

Thanos は Prometheus のデータを長期保存するための CNCF プロジェクト:

┌─────────────────────────────────────────────────────┐
│                   Thanos Query                       │
│           (PromQL エンドポイント)                     │
└──────┬──────────────┬──────────────┬────────────────┘
       │              │              │
       ▼              ▼              ▼
┌──────────┐   ┌──────────┐   ┌──────────┐
│ Thanos    │   │ Thanos    │   │ Thanos   │
│ Sidecar   │   │ Sidecar   │   │ Store    │
│ (Prom A)  │   │ (Prom B)  │   │ Gateway  │
└──────────┘   └──────────┘   └──────────┘
                                     │
                                     ▼
                              ┌──────────┐
                              │ Object   │
                              │ Storage  │
                              │ (S3/GCS) │
                              └──────────┘

主要コンポーネント:

  • Sidecar: Prometheus に併設し、データをオブジェクトストレージにアップロード
  • Store Gateway: オブジェクトストレージからデータを読み取り
  • Query: 複数のデータソースに対して PromQL を統合実行
  • Compactor: ダウンサンプリングとブロックのコンパクション
  • Ruler: 長期データに対する Recording Rules と Alerting Rules
# Thanos Sidecar の設定例(Kubernetes)
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: prometheus
spec:
  template:
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.48.0
          args:
            - "--storage.tsdb.min-block-duration=2h"
            - "--storage.tsdb.max-block-duration=2h"
          volumeMounts:
            - name: data
              mountPath: /prometheus

        - name: thanos-sidecar
          image: thanosio/thanos:v0.33.0
          args:
            - "sidecar"
            - "--tsdb.path=/prometheus"
            - "--prometheus.url=http://localhost:9090"
            - "--objstore.config-file=/etc/thanos/bucket.yml"
          volumeMounts:
            - name: data
              mountPath: /prometheus
            - name: thanos-config
              mountPath: /etc/thanos
# bucket.yml(S3 設定例)
type: S3
config:
  bucket: "thanos-metrics"
  endpoint: "s3.us-west-2.amazonaws.com"
  region: "us-west-2"
  access_key: "${AWS_ACCESS_KEY_ID}"
  secret_key: "${AWS_SECRET_ACCESS_KEY}"

Grafana Mimir

Grafana Labs が開発する水平スケーラブルな長期保存ソリューション:

# Mimir へのリモートライト
# prometheus.yml
remote_write:
  - url: http://mimir:9009/api/v1/push
    headers:
      X-Scope-OrgID: "my-org"

特徴:

  • マルチテナント対応
  • 水平スケーリング
  • PromQL 100% 互換
  • コストの最適化(S3/GCS への保存)

VictoriaMetrics

高性能な Prometheus 互換の長期保存ソリューション:

# VictoriaMetrics へのリモートライト
remote_write:
  - url: http://victoriametrics:8428/api/v1/write

特徴:

  • 高い圧縮率(Prometheus の 3〜10 倍効率的)
  • PromQL 互換 + 拡張(MetricsQL)
  • シンプルな運用

14.2 PromQL 互換言語

MetricsQL(VictoriaMetrics)

VictoriaMetrics が提供する PromQL の上位互換:

# PromQL にない追加関数
# ロールアップウィンドウの自動調整
rate(http_requests_total)  # [5m] を省略可能

# range_median — 中央値の効率的な計算
range_median(http_request_duration_seconds[5m])

# label_set — ラベルの追加
label_set(up, "env", "production")

# WITH テンプレート(マクロ)
WITH (
  error_rate = sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
)
error_rate > 0.05

LogQL(Grafana Loki)

PromQL にインスパイアされたログクエリ言語:

# ログからメトリクスを抽出
sum by (status) (rate({job="api"} | json | status >= 400 [5m]))

# PromQL と同様の構文でログベースのメトリクスを操作
sum(count_over_time({job="api"} |= "error" [5m])) by (host)

PromQL in OpenTelemetry

OpenTelemetry Collector で Prometheus メトリクスを収集し、PromQL で問い合わせ可能:

# otel-collector-config.yml
receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: "otel-collector"
          static_configs:
            - targets: ["localhost:8888"]

exporters:
  prometheusremotewrite:
    endpoint: "http://prometheus:9090/api/v1/write"

service:
  pipelines:
    metrics:
      receivers: [prometheus]
      exporters: [prometheusremotewrite]

14.3 Grafana との統合

データソース設定

# Grafana のデータソースプロビジョニング
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    jsonData:
      timeInterval: "15s"   # scrape_interval と一致させる
      queryTimeout: "60s"
      httpMethod: POST

  - name: Thanos
    type: prometheus
    access: proxy
    url: http://thanos-query:9090
    jsonData:
      timeInterval: "15s"
      customQueryParameters: "dedup=true&partial_response=true"

ダッシュボードの例(JSON Model)

{
  "panels": [
    {
      "title": "Request Rate",
      "type": "timeseries",
      "datasource": "Prometheus",
      "targets": [
        {
          "expr": "sum by (handler) (rate(http_requests_total{job=\"$job\"}[$__rate_interval]))",
          "legendFormat": "{{ handler }}"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "reqps"
        }
      }
    },
    {
      "title": "Error Rate",
      "type": "stat",
      "targets": [
        {
          "expr": "sum(rate(http_requests_total{status=~\"5..\", job=\"$job\"}[$__rate_interval])) / sum(rate(http_requests_total{job=\"$job\"}[$__rate_interval])) * 100",
          "legendFormat": "Error %"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "thresholds": {
            "steps": [
              {"color": "green", "value": null},
              {"color": "yellow", "value": 1},
              {"color": "red", "value": 5}
            ]
          }
        }
      }
    }
  ]
}

14.4 Federation(フェデレーション)

複数の Prometheus サーバ間でメトリクスを共有する仕組み:

# 上位 Prometheus の設定(グローバルビュー)
scrape_configs:
  - job_name: "federate"
    scrape_interval: 60s
    honor_labels: true
    metrics_path: "/federate"
    params:
      "match[]":
        - '{job="api"}'
        - '{__name__=~"job:.*"}'  # Recording Rules の結果
    static_configs:
      - targets:
          - "prometheus-dc1:9090"
          - "prometheus-dc2:9090"

14.5 Prometheus Operator(Kubernetes)

# PrometheusRule CRD で Recording Rules を管理
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: http-rules
  labels:
    release: prometheus
spec:
  groups:
    - name: http.rules
      rules:
        - record: job:http_requests:rate5m
          expr: sum by (job) (rate(http_requests_total[5m]))
        - alert: HighErrorRate
          expr: |
            sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
              / sum by (job) (rate(http_requests_total[5m])) > 0.05
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "High error rate on {{ $labels.job }}"

---
# ServiceMonitor でスクレイプ対象を定義
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: api-server
  labels:
    release: prometheus
spec:
  selector:
    matchLabels:
      app: api-server
  endpoints:
    - port: metrics
      interval: 15s
      path: /metrics

15. まとめとリファレンス

15.1 PromQL チートシート

セレクタ

metric_name                              # メトリクス名で選択
metric_name{label="value"}              # ラベル完全一致
metric_name{label!="value"}             # ラベル不一致
metric_name{label=~"regex"}             # 正規表現一致
metric_name{label!~"regex"}             # 正規表現不一致
metric_name[5m]                          # レンジベクトル(過去5分)
metric_name offset 1h                    # 1時間前のデータ
metric_name @ 1704067200                 # 特定タイムスタンプ

よく使う関数

rate(counter[5m])                        # 1秒あたりの増加率
increase(counter[1h])                    # 期間内の増加量
irate(counter[5m])                       # 瞬間増加率
delta(gauge[1h])                         # Gaugeの差分
deriv(gauge[1h])                         # Gaugeの変化率
predict_linear(gauge[6h], 3600)          # 1時間後の予測値
histogram_quantile(0.99, rate(buckets[5m]))  # 99パーセンタイル
absent(metric)                           # メトリクスが存在しなければ1
absent_over_time(metric[5m])             # 期間内にデータなしなら1
changes(gauge[1h])                       # 値の変化回数
resets(counter[1h])                      # カウンターリセット回数

集約

sum by (label) (metric)                  # ラベル別合計
avg by (label) (metric)                  # ラベル別平均
max by (label) (metric)                  # ラベル別最大
min by (label) (metric)                  # ラベル別最小
count by (label) (metric)               # ラベル別カウント
topk(5, metric)                          # 上位5件
bottomk(5, metric)                       # 下位5件
quantile(0.95, metric)                   # 95パーセンタイル
count_values("name", metric)             # 値別カウント

演算子

# 算術: +, -, *, /, %, ^
# 比較: ==, !=, <, >, <=, >=
# 論理: and, or, unless
# ベクトルマッチング: on(), ignoring(), group_left(), group_right()
# bool修飾子: metric > bool 100

15.2 アラート設計のベストプラクティスまとめ

  1. 症状ベースのアラート: 原因ではなく、ユーザーに影響する症状をアラートにする
  2. マルチウィンドウ: 短い窓と長い窓の両方で条件を確認(誤報削減)
  3. 適切な for 期間: 一時的なスパイクを除外しつつ、反応速度を維持
  4. Recording Rules の活用: アラートで使うクエリは事前計算
  5. Runbook URL の添付: すべてのアラートに対応手順へのリンクを付ける
  6. ラベルの活用: severity, team, service などのラベルでルーティング
  7. SLO ベースのアラート: エラーバジェットの消費率に基づくアラート

15.3 PromQL のバージョン別新機能

バージョン機能
Prometheus 2.7サブクエリ
Prometheus 2.16group 集約演算子
Prometheus 2.26@ 修飾子、負のオフセット
Prometheus 2.31三角関数 (sin, cos, atan2 など)
Prometheus 2.33present_over_time(), last_over_time()
Prometheus 2.36sgn() 関数
Prometheus 2.40Native Histograms(実験的)
Prometheus 2.41limitk(), limit_ratio()
Prometheus 2.43info() 関数(実験的)
Prometheus 2.47mad_over_time()
Prometheus 2.50PromQL エンジンの改善、UTF-8 メトリクス名
Prometheus 2.53sort_by_label(), sort_by_label_desc()

15.4 公式リファレンス

ドキュメント

ツール

  • promtool: Prometheus 付属の CLI ツール(ルール検証、テスト)
  • Grafana Explore: アドホックなクエリ探索に最適
  • PromLens: PromQL のビジュアルクエリビルダー(https://promlens.com)

学習リソース

  • Prometheus: Up & Running (Julien Pivotto, Brian Brazil) — O'Reilly
  • Google SRE Book — SLI/SLO/SLA の設計指針
  • Awesome Prometheus — コミュニティ管理のリソース集
  • Robust Perception Blog — Prometheus の共同創設者によるブログ

15.5 総括

PromQL は、時系列データの操作に特化した強力なクエリ言語である。本記事で解説した内容を振り返ると:

  1. データモデルの理解: メトリクス名、ラベル、4つのメトリクスタイプが基礎
  2. セレクタの習得: インスタントベクトル、レンジベクトル、マッチャーの使い分け
  3. 関数の活用: rate(), histogram_quantile(), predict_linear() などの適切な選択
  4. 集約の設計: by/without による適切なグループ化
  5. ベクトルマッチング: on/ignoring, group_left/group_right によるジョイン操作
  6. Recording Rules: パフォーマンスと可読性の向上
  7. Alerting Rules: SLO ベースの効果的なアラート設計
  8. エコシステム: Thanos, Mimir, VictoriaMetrics との連携による長期保存

PromQL を習得することで、クラウドネイティブ環境における可観測性の基盤を構築し、信頼性の高いシステム運用を実現できる。日々の運用で様々なクエリパターンを試し、Recording Rules とアラートルールを継続的に改善していくことが、効果的なモニタリングへの道筋である。