- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 1. 概要
- 2. クエリ処理パイプライン全体
- 3. レキサー(Lexer)
- 4. パーサー(Parser)
- 5. AST(Abstract Syntax Tree)
- 6. クエリ評価エンジン
- 7. 関数評価
- 8. ベクターマッチング(Binary Operations)
- 9. サブクエリ
- 10. メモリ管理
- 11. Native Histogram処理
- 12. パフォーマンス最適化技法
- 13. エラー処理と制限事項
- 14. まとめ
1. 概要
PromQL(Prometheus Query Language)はPrometheusのコアクエリ言語であり、時系列データの選択と集約のための関数型式言語です。この記事ではPromQLエンジンの内部構造をソースコードレベルで分析します。
PromQLエンジンのコードはpromql/パッケージに位置し、パーサー(parser)、AST(Abstract Syntax Tree)、評価エンジン(engine)で構成されます。
2. クエリ処理パイプライン全体
PromQL文字列
|
v
+----------+
| Lexer | (トークン化)
+----------+
|
v
+----------+
| Parser | (構文解析)
+----------+
|
v
+----------+
| AST | (抽象構文木)
+----------+
|
v
+----------+
| Analyzer | (最適化/検証)
+----------+
|
v
+----------+
| Engine | (評価/実行)
+----------+
|
v
結果(Matrix/Vector/Scalar)
3. レキサー(Lexer)
3.1 トークン化過程
レキサーはPromQL文字列をトークンストリームに変換します:
入力: rate(http_requests_total[5m]) > 10
トークンストリーム:
IDENTIFIER("rate")
LEFT_PAREN
IDENTIFIER("http_requests_total")
LEFT_BRACKET
DURATION("5m")
RIGHT_BRACKET
RIGHT_PAREN
GTR
NUMBER(10)
3.2 トークンタイプ
PromQLレキサーが認識する主要トークンタイプ:
リテラル:
NUMBER - 整数/浮動小数点(例: 42, 3.14)
STRING - 文字列リテラル(例: "hello")
DURATION - 時間範囲(例: 5m, 1h, 30s)
識別子:
IDENTIFIER - メトリクス名、ラベル名
METRIC_IDENTIFIER - メトリクス識別子
演算子:
ADD, SUB, MUL, DIV, MOD, POW
EQL, NEQ, LSS, GTR, LTE, GTE
AND, OR, UNLESS
キーワード:
BY, WITHOUT, ON, IGNORING
GROUP_LEFT, GROUP_RIGHT
OFFSET, BOOL
SUM, AVG, MIN, MAX, COUNT, ...
3.3 レキサー実装の特性
レキサーはGoの手動ステートマシン方式で実装されています。重要な特性:
- 1文字先読み: 現在の文字と次の文字を確認してトークン境界を判定
- durationパース: 数字の後に時間単位(ms, s, m, h, d, w, y)が続くとDURATIONトークンとして認識
- 文字列エスケープ: シングルクォート、ダブルクォート、バッククォート文字列リテラルをサポート
- コメント非対応: PromQLにはコメント構文がない
4. パーサー(Parser)
4.1 パーサー構造
PromQLパーサーはPrattパーサー(Top-Down Operator Precedence)方式を使用します:
パーサー動作フロー:
1. トークンストリームから次のトークンを読み取り
2. prefix解析関数呼び出し(リテラル、単項演算、括弧など)
3. 現在の優先度より高いinfix演算子があればinfix解析
4. ASTノードを返す
4.2 演算子優先順位
優先順位(低い順):
1. OR
2. AND, UNLESS
3. ==, !=, <, >, <=, >=(比較)
4. +, -(加減算)
5. *, /, %(乗除算)
6. ^(べき乗、右結合)
7. 単項 +, -
5. AST(Abstract Syntax Tree)
5.1 ノードタイプ
ASTは以下のノードタイプで構成されます:
Expr(式インターフェース)
|
+-- NumberLiteral (数値リテラル)
+-- StringLiteral (文字列リテラル)
+-- VectorSelector (即時ベクターセレクタ)
+-- MatrixSelector (範囲ベクターセレクタ)
+-- AggregateExpr (集約式)
+-- BinaryExpr (二項演算式)
+-- UnaryExpr (単項演算式)
+-- Call (関数呼び出し)
+-- ParenExpr (括弧式)
+-- SubqueryExpr (サブクエリ式)
+-- StepInvariantExpr (step不変式)
5.2 AST例
クエリ: sum(rate(http_requests_total[5m])) by (job)
AggregateExpr (sum, by=[job])
|
+-- Call (rate)
|
+-- MatrixSelector
|
+-- VectorSelector (http_requests_total)
+-- Range: 5m
6. クエリ評価エンジン
6.1 エンジン構成
Engine:
+-- queryable (TSDBストレージインターフェース)
+-- maxSamples (最大サンプル数制限)
+-- timeout (クエリタイムアウト)
+-- activeQueries (アクティブクエリカウンタ)
+-- maxConcurrency (最大同時クエリ数)
6.2 Instant Query vs Range Query
Instant Query: 単一時点での評価
リクエスト: GET /api/v1/query?query=up&time=1609459200
処理:
1. AST生成
2. time=1609459200で一度評価
3. 結果: Vector(各時系列に対する単一値)
Range Query: 時間範囲でのstep別評価
リクエスト: GET /api/v1/query_range?query=up&start=1609455600&end=1609459200&step=60
処理:
1. AST生成
2. startからendまでstep(60秒)間隔で繰り返し評価
3. 各stepでinstant queryのように評価
4. 結果: Matrix(各時系列に対する時系列値配列)
6.3 Step評価メカニズム
Range Queryの各stepは独立したinstant evaluationです:
query: rate(http_requests_total[5m])
start: T0, end: T0+300s, step: 60s
Step評価:
T0: rate(T0-5mからT0までのサンプル)
T0+60: rate(T0+60-5mからT0+60までのサンプル)
T0+120: rate(T0+120-5mからT0+120までのサンプル)
T0+180: rate(T0+180-5mからT0+180までのサンプル)
T0+240: rate(T0+240-5mからT0+240までのサンプル)
T0+300: rate(T0+300-5mからT0+300までのサンプル)
6.4 Lookback Delta
Lookback Delta(デフォルト5分)はinstant vectorセレクタがサンプルを検索する時間ウィンドウです:
評価時点: T
lookback delta: 5m
サンプル検索範囲: (T - 5m, T]
動作:
1. T以前5分以内の最新サンプルを検索
2. そのサンプルがstale markerでなければ結果に含む
3. 5分以内にサンプルがなければその時系列を除外
重要: 正確にT時点のサンプルがなくてもよい
lookback delta内の最新サンプルを使用
7. 関数評価
7.1 関数レジストリ
すべてのPromQL関数は中央レジストリに登録されます:
関数登録情報:
Name: "rate"
ArgTypes: [ValueTypeMatrix] (引数タイプ)
ReturnType: ValueTypeVector (戻り値タイプ)
Call: funcRate (実際の実装関数)
Variadic: 0 (可変引数数)
7.2 rate()関数の実装
rate()の内部動作:
rate(counter[5m])の評価:
1. 5分範囲のサンプル抽出: [s1, s2, s3, ..., sN]
2. Counterリセット検知と補正:
- 値が減少したら前の値を累積に加算
3. 補正された総増加量計算:
resultValue = (last - first) + counterCorrection
4. 外挿(extrapolation):
- 範囲開始/終了を超える時間比率計算
5. 秒あたりの増加率:
result = extrapolatedIncrease / rangeDurationSeconds
7.3 histogram_quantile()の実装
histogram_quantile(phi, buckets)の評価:
1. leラベルでバケットをソート
2. 各バケットの累積カウントを収集
3. phi(例: 0.95) * total_count = target_countを計算
4. target_countを含むバケットを検索
5. バケット境界間で線形補間:
bucketStart + (bucketEnd - bucketStart) *
(target - countBelow) / (countInBucket)
7.4 集約関数の評価
sum by (job) (metric)の評価:
1. metricのすべての時系列を収集
2. jobラベル値でグルーピング
3. 各グループ内で値を合算
4. 結果時系列: jobラベルのみ保持
実装:
- ハッシュマップでグループ管理
- ラベルセットのハッシュをキーとして使用
- 衝突時は正確なラベル比較
8. ベクターマッチング(Binary Operations)
8.1 一対一マッチング
metric_a + metric_b
マッチングアルゴリズム:
1. 両方のベクターの時系列をラベルセットでインデックス
2. マッチングラベルセットが同一のペアを検索
3. 演算実行(ここでは値の加算)
4. 結果時系列に左側のラベルセットを使用
パフォーマンス:
- ハッシュジョイン方式でO(n+m)時間計算量
- 小さい方をハッシュテーブルとして構築
8.2 多対一/一対多マッチング
metric_a * on(job) group_left metric_b
処理:
1. on(job)でマッチングキーを決定
2. metric_b(one側)をjob別ハッシュマップにインデックス
3. metric_a(many側)の各時系列に対して:
a. jobラベルでmetric_bのマッチング項目を検索
b. 演算実行
c. metric_aのすべてのラベルを保持(group_left)
9. サブクエリ
9.1 サブクエリ処理
max_over_time(rate(http_requests_total[5m])[30m:1m])
評価過程:
1. 外部関数(max_over_time)がrange vectorを必要
2. サブクエリ部分: rate(http_requests_total[5m])[30m:1m]
3. 内部評価:
- 現在時点(T)の30分前からTまで
- 1分間隔でrate(http_requests_total[5m])を評価
- 30個のinstant vector結果を生成
4. range vectorに変換
5. max_over_timeが各時系列の最大値を計算
9.2 サブクエリ最適化
サブクエリ評価の主要最適化:
1. 内部クエリのrange vectorを再利用(同一時系列)
2. stepアラインメント:サブクエリのstepを外部stepに整列
3. resolutionが指定されなければglobal evaluation_intervalを使用
10. メモリ管理
10.1 サンプルカウンティング
--query.max-samples(デフォルト50,000,000)
カウント方式:
- 各stepでロード/生成されたサンプル数を累積
- range vectorの場合は範囲内のすべてのサンプルをカウント
- 制限超過時にクエリを即座に中断
10.2 クエリ同時実行制御
--query.max-concurrency(デフォルト20)
実装:
- セマフォで同時クエリ数を制限
- 新クエリがセマフォ取得を待機
- タイムアウト内に取得失敗時にエラーを返す
10.3 ポイントプール
PromQLエンジンはメモリ割り当てを最小化するためにポイントプールを使用します:
ポイントプール動作:
1. クエリ開始時にポイントスライスをプールから割り当て
2. step評価間で再利用
3. クエリ完了時にプールに返却
4. sync.Poolを使用してGC負担を軽減
11. Native Histogram処理
Native Histogramは既存のfloatサンプルと異なる処理パス:
1. ヒストグラムスキーマ(resolution)の整列
2. バケット単位でのマージ
3. 集約時にスキーマ変換が必要な場合は自動実行
sum(native_histogram_metric)の評価:
1. 各時系列のヒストグラムを収集
2. スキーマが異なる場合はより低い解像度に統一
3. バケット別カウントを合算
4. sumとcountフィールドを合算
12. パフォーマンス最適化技法
12.1 Series Set マージ
複数ブロックにまたがる同一時系列が存在する場合:
1. 各ブロックで時系列集合を検索
2. マージソートで統合
3. チャンクレベルで時間範囲フィルタリング
4. 重複排除
12.2 StepInvariantExpr最適化
stepに関係なく同一結果を返す式を検知:
- 数値リテラル
- offsetのないスカラー関数
- 特定の集約結果
検知された場合:
- 一度だけ評価してすべてのstepに結果をコピー
- 不要な繰り返し評価を排除
12.3 クエリ前処理
最適化パス:
1. 定数畳み込み(constant folding): 2 + 3 -> 5
2. ラベルマッチング最適化:インデックスヒント生成
3. 不要なラベル演算の除去
4. 空の結果の早期リターン
13. エラー処理と制限事項
13.1 一般的なエラー状況
1. "query processing would load too many samples"
- max-samples制限超過
- 解決:クエリ範囲の縮小または制限の増加
2. "query timed out in expression evaluation"
- query.timeout超過
- 解決:クエリ最適化またはタイムアウトの増加
3. "found duplicate series for the match group"
- ベクターマッチングで多対一関係にgroup_left/right未指定
- 解決:group_leftまたはgroup_rightの追加
4. "many-to-many matching not allowed"
- 両側とも多数のマッチングはサポートされない
- 解決:集約で片側を一(one)に縮小
14. まとめ
PromQLエンジンは時系列データに特化した関数型クエリ言語を効率的に処理するシステムです。Prattパーサーによるクリーンな構文解析、stepベースのrange query評価、lookback deltaによる柔軟なサンプル検索、ポイントプールとStepInvariantExpr最適化によるメモリ効率がコアです。
次の記事ではPrometheusのサービスディスカバリメカニズムを分析します。kubernetes_sdのwatchメカニズム、リラベリングの動作原理、ターゲットライフサイクルを解説する予定です。