グラフはデータではなくデータの要約だ
一言でいうと
ダッシュボードはデータではなく、データの要約です。要約の方法が2つあれば答えも2つ出て、どちらも正しいのです。したがって「グラフに見えなかった」は「起きなかった」ではありません。
なぜ必要なのか
事故対応の会議で、こんな言葉が飛び交います。「エラー率が28%まで上がりました」「うちのダッシュボードでは10%に見えますが」。2人は同じPrometheusを見ていて、どちらもクエリを間違えていません。1人は5分のウィンドウで、もう1人は1時間のウィンドウで問い合わせただけです。
このずれは些細に見えて、決定を変えます。28%はエラーバジェットを1日分燃やす数字で、10%は「様子を見よう」という数字です。アラートが15%に設定されていたなら、一方のグラフでは鳴るべきで、もう一方では鳴る理由がありません。実際に事故のあとで「なぜアラートが鳴らなかったのか」を掘り下げると、ルールのしきい値ではなく、ルールが使ったウィンドウ長が犯人であることがよくあります。
反対方向の思い違いもあります。データがあるのにグラフが空になるケースです。スクレイプ間隔が15秒のメトリクスにrate(...[15s])を使うと、ウィンドウ内にサンプルが1つしかなく変化率を出せないため、Prometheusはエラーではなく空の結果を返します。画面には「No data」と表示されます。それを見て「トラフィックが途絶えた」と読むと、存在しない障害を作り出してしまいます。
どう動くのか
レンジクエリ(/api/v1/query_range)は、3つを受け取ります。start・end・stepです。Prometheusはstartからendまでstep間隔のグリッドを作り、グリッド点ごとにインスタントクエリを1回ずつ実行して、その値をつなぎます。グラフの線はデータではなく、このグリッド点です。
ここにrate(...[창])が付くと(プレースホルダーはウィンドウです)、各グリッド点の値はその点の直前のウィンドウ長ぶんの平均変化率になります。そのため、ウィンドウはローパスフィルターとして働きます。20分間の急増を1時間のウィンドウで見ると、値がおよそ3分の1に押しつぶされ、代わりに急増がウィンドウ内に入っている時間だけ左右に広がって、幅が広くなります。高さと幅は変わりますが、下の面積はほぼ保存されます。そのため「合計で何件か」は合うのに、「どれだけひどかったか」だけが間違ってしまいます。
ウィンドウには下限もあります。Prometheusのクエリ関数のドキュメントとGrafanaは、スクレイプ間隔の4倍以上を推奨しています。Grafanaの$__rate_intervalは、このルールを自動化した変数で、max($__interval + scrape_interval, 4 * scrape_interval)で計算されます。ここで$__intervalは、パネルの時間範囲をパネルのピクセル幅で割った値です。つまりパネルを広く見ると、ウィンドウがひとりでに広がります。同じパネルを12時間で見ていたものを30日に変えると、ピークが自然に低くなる理由がこれです。
集計にも同じ落とし穴があります。avgで比率を平均すると、トラフィックの少ない時間と多い時間が同じ重みを持ちます。全体の比率を知りたいなら、合計を合計で割らなければなりません。分位数はもっとひどく、パーセンタイルはそもそも平均できる値ではありません。ヒストグラムは、バケットを先にsum by (le)で合算してからhistogram_quantileを呼ぶ必要があり、すでに出たp99を平均すると、何の意味もない数字になります。
increase()とrate()は、ウィンドウの両端で外挿します。サンプルがウィンドウの境界にぴったり置かれることが珍しいからで、その結果increase()は、リクエスト数のように整数であるべき値を、20316.33のような小数で返します。パネルにそのまま表示すると、「0.33件って何?」という質問を受けます。
| 知りたいこと | 使うべきもの | よく使ってしまうもの |
|---|---|---|
| 最悪のときにいくらだったか | 短いウィンドウ + 細かいstep | パネルのデフォルトのまま |
| 全期間の比率 | 合計 ÷ 合計 | 比率の平均 |
| テールレイテンシ | sum by (le)のあとにhistogram_quantile |
分位数の平均 |
| 正確な件数 | カウンターの差 | increase()の小数点 |
現場での姿
あるチームは、30日のダッシュボードをそのまま事故の振り返りに使いました。そのパネルの$__rate_intervalは2時間を超えていて、20分間の事故は、線の上のごく小さなこぶとしてしか残っていませんでした。振り返りの結論は「影響は大きくなかった」でした。同じ事故を6時間の範囲で開き直すと、ピークが28%でそびえていました。変わったのは、ブラウザのアドレスバーの時間範囲だけでした。
別のチームでは、「昨夜の失敗件数」パネルが20316.33を表示していました。担当者は小数点を消すためにround()をかぶせ、それ以降、その数字が外挿値であることを誰も知りませんでした。カウンターがリセットされた区間で、そのパネルは静かに間違った値を見せ続けていました。
次のラボですること
レンジクエリを投げて、点の数・最大値・しきい値超過数を出力する小さなツールを作り、そのツールで同じ12時間をさまざまな方法で問い合わせます。ウィンドウを15秒に縮めてグラフが空になるのを見て、ウィンドウを1分から1時間まで広げてピークが低くなり幅が広がるのを測り、stepから$__rate_intervalを自分で計算して、パネルの幅がウィンドウを決める過程を再現します。比率の平均と合計 ÷ 合計を比較し、長いウィンドウのp99がなぜ最悪のp99ではないのかを確認したあと、最後に本番のダッシュボードからコピーしてきた壊れたパネル3つを直して提出します。採点ツールが、直したクエリを実際に投げて、値で判定します。