TT Lab
はじめる
学ぶ 学習パス コース

可観測性

同じ障害がダッシュボードでは見えなかった

TT Labで続きを見る

目標

同じ12時間のデータに同じ質問を投げながら、rateウィンドウとstepだけを変えて、答えがどう変わるかを数字で測り、本番のダッシュボードからコピーしてきた壊れたパネル3つを直して提出します。

なぜ重要なのか

ダッシュボードを信じるということは、そのパネルがどんな要約をしているかを知っているという意味でなければなりません。ところが大半のパネルは、誰かが一度作ったあと、誰もクエリを開いて見ません。ウィンドウが1時間なら、20分間の事故は3分の1の高さに押しつぶされて現れ、ウィンドウがスクレイプ間隔より短ければ、データがあるのに画面に「データなし」と表示されます。比率を平均すると、明け方の1時間と昼の1時間が同じ重みを持ち、分位数を平均すると、何の意味もない数字が出ます。このラボの目的は、PromQLをさらに学ぶことではなく、パネル1つを見て、「この絵は何を消したのか」を尋ねられるようになることです。

ステップ

  1. /root/obs-graph-traps/qr.pyを作成してください。python3 qr.py '<PromQL>' <step초> [<범위 시간>] [<임계값>]で呼び出すと(プレースホルダーはstepの秒数、範囲の時間、しきい値です)、/api/v1/query_rangeにstart=지금-범위・end=지금・step=step초を渡して1回投げ(プレースホルダーは、今-範囲、今、step秒です)、points=<돌아온 값의 총 개수> max=<최댓값> over=<임계값보다 큰 값의 개수>を1行出力します(プレースホルダーは、返ってきた値の総数、最大値、しきい値より大きい値の個数です)。範囲のデフォルトは12(時間)、しきい値のデフォルトは0.01です。値が1つもなければ、points=0 max=NA over=0を出力します。最大値は小数第6位まで書きます。
  2. 5xx率を尋ねる式sum(rate(http_requests_total{job="shop-api",status="500"}[창])) / sum(rate(http_requests_total{job="shop-api"}[창]))を、ウィンドウだけを変えて4回投げてください(プレースホルダーはウィンドウです)。ウィンドウは15s・30s・1m・5m、stepは60、範囲は12時間です。/root/obs-graph-traps/window-points.tsvにヘッダーなしで4行、各行はタブで区切った2列<창> <점 수>です(プレースホルダーはウィンドウと点の数です)。そして/root/obs-graph-traps/02-why.txtに、reason=で始まる1行を40文字以上で書き、最も短いウィンドウで点がない理由を説明してください。
  3. 同じ5xx率の式を、ウィンドウ1m・5m・15m・30m・1hで5回投げてください(step 60、範囲12時間、しきい値0.01)。/root/obs-graph-traps/window-shape.tsvにヘッダーなしで5行、各行はタブで区切った3列<창> <최댓값> <0.01 을 넘은 점 수>です(プレースホルダーはウィンドウ、最大値、0.01を超えた点の数です)。最大値は小数第6位、点の数は整数です。
  4. Grafanaは$__rate_intervalを、max($__interval + 스크레이프간격, 4 * 스크레이프간격)で計算します(プレースホルダーはスクレイプ間隔です)。このPodのスクレイプ間隔は15秒です。stepが15・108・600・3600秒のときのrateウィンドウをそれぞれ計算したあと、そのウィンドウで5xx率の式を同じstepで投げてください(範囲12時間)。/root/obs-graph-traps/rate-interval.tsvにヘッダーなしで4行、各行はタブで区切った4列<step초> <rate 창 초> <점 수> <최댓값>です(プレースホルダーはstepの秒数、rateウィンドウの秒数、点の数、最大値です)。そして/root/obs-graph-traps/04-note.txtに、reason=で始まる1行を40文字以上で書き、step 3600のときだけ、同じクエリを投げ直すたびに最大値が変わる理由を説明してください。
  5. 同じ12時間の5xx率を、2通りで求めてください。1つは1分間隔の比率の単純平均avg_over_time((sum(rate(http_requests_total{job="shop-api",status="500"}[5m])) / sum(rate(http_requests_total{job="shop-api"}[5m])))[12h:1m])、もう1つはトラフィックで重みを付けた全体の比率sum(increase(http_requests_total{job="shop-api",status="500"}[12h])) / sum(increase(http_requests_total{job="shop-api"}[12h]))です。/root/obs-graph-traps/weighted.txtに3行を書いてください。avg_of_ratio=<소수 여섯 자리>、weighted_ratio=<소수 여섯 자리>、gap_pct=<소수 두 자리>です(プレースホルダーは小数第6位、小数第6位、小数第2位です)。gap_pctは(前の値 − 後の値) ÷ 後の値 × 100です。
  6. histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[창])))を、ウィンドウ5m・15m・12hで3回投げてください(step 60、範囲12時間。プレースホルダーはウィンドウです)。/root/obs-graph-traps/p99-window.tsvにヘッダーなしで3行、各行はタブで区切った2列<창> <12시간 최댓값>です(プレースホルダーはウィンドウと12時間の最大値です)。そして/root/obs-graph-traps/06-note.txtに、reason=で始まる1行を40文字以上で書き、長いウィンドウの分位数が、短いウィンドウの分位数の最大値より小さくなる理由を説明してください。
  7. /opt/lab/graph/panels.ymlのパネルp1は「過去12時間の5xx件数」を尋ねていますが、小数点が付いた値を出します。/root/obs-graph-traps/fix-count.promqlに、同じ12時間の5xx件数を整数で出すPromQLを書いてください(コメントの#は残してもかまいません)。そして/root/obs-graph-traps/count-panel.txtに2行を書いてください。broken_value=<소수 세 자리>はp1の式が今出す値、fixed_value=<정수>は直したクエリが出す値です(プレースホルダーは小数第3位と整数です)。採点ツールは、直したクエリを実際に投げて値を確認します。
  8. /opt/lab/graph/panels.ymlのパネルp2(エラー率)とp3(p99レイテンシ)は、それぞれ「最も高かったときにいくらだったか」に答えられません。/root/obs-graph-traps/fix-ratio.promqlと/root/obs-graph-traps/fix-p99.promqlに、直したクエリを書いてください。そして/root/obs-graph-traps/verdict.tsvにヘッダーなしで2行、各行はタブで区切った4列<패널id> <고치기 전 12시간 최댓값> <고친 뒤 12시간 최댓값> <무엇이 틀렸었나>です(プレースホルダーはパネルid、修正前の12時間の最大値、修正後の12時間の最大値、何が間違っていたかです)。1行目はp2、2行目はp3で、最大値はstep 60・範囲12時間で測り、小数第6位までです。4列目は20文字以上で、数字を1つ以上含める必要があります。

参考

レンジクエリを数字で要約するツールを作る

/root/obs-graph-traps/qr.pyを作成してください。python3 qr.py '<PromQL>' <step초> [<범위 시간>] [<임계값>]で呼び出すと(プレースホルダーはstepの秒数、範囲の時間、しきい値です)、/api/v1/query_rangeにstart=지금-범위・end=지금・step=step초を渡して1回投げ(プレースホルダーは、今-範囲、今、step秒です)、points=<돌아온 값의 총 개수> max=<최댓값> over=<임계값보다 큰 값의 개수>を1行出力します(プレースホルダーは、返ってきた値の総数、最大値、しきい値より大きい値の個数です)。範囲のデフォルトは12(時間)、しきい値のデフォルトは0.01です。値が1つもなければ、points=0 max=NA over=0を出力します。最大値は小数第6位まで書きます。

レンジの応答は、data.result[].values[][1]に文字列で入っています。時系列が複数あれば、すべて合わせて数えてください。NaNという文字列が混ざってくることがあるので、math.isnanで除外しないと最大値が壊れます。標準ライブラリだけを使います(urllib.request、json、time、math)。

空のグラフは、トラフィックがないという意味ではない

5xx率を尋ねる式sum(rate(http_requests_total{job="shop-api",status="500"}[창])) / sum(rate(http_requests_total{job="shop-api"}[창]))を、ウィンドウだけを変えて4回投げてください(プレースホルダーはウィンドウです)。ウィンドウは15s・30s・1m・5m、stepは60、範囲は12時間です。/root/obs-graph-traps/window-points.tsvにヘッダーなしで4行、各行はタブで区切った2列<창> <점 수>です(プレースホルダーはウィンドウと点の数です)。そして/root/obs-graph-traps/02-why.txtに、reason=で始まる1行を40文字以上で書き、最も短いウィンドウで点がない理由を説明してください。

このPodのスクレイプ間隔は15秒です。rateは、ウィンドウ内にサンプルが2つ以上ないと変化率を出せず、1つしかなければエラーではなく空の結果を返します。そのため公式ドキュメントは、ウィンドウをスクレイプ間隔の4倍以上にするよう述べています。バックフィルされた区間の解像度は30秒なので、30秒のウィンドウもほとんど空です。

ウィンドウを広げると、ピークは低くなり、幅は広がる

同じ5xx率の式を、ウィンドウ1m・5m・15m・30m・1hで5回投げてください(step 60、範囲12時間、しきい値0.01)。/root/obs-graph-traps/window-shape.tsvにヘッダーなしで5行、各行はタブで区切った3列<창> <최댓값> <0.01 을 넘은 점 수>です(プレースホルダーはウィンドウ、最大値、0.01を超えた点の数です)。最大値は小数第6位、点の数は整数です。

stepを60に固定したので、1点がそのまま1分です。3列目は、「グラフが語る事故の長さ(分)」として読めばよいのです。高さが何分の1かに縮むあいだに、幅が何倍に広がるか、両方向を一緒に見てください。

パネルの幅がウィンドウを決める: $__rate_intervalを手で計算する

Grafanaは$__rate_intervalを、max($__interval + 스크레이프간격, 4 * 스크레이프간격)で計算します(プレースホルダーはスクレイプ間隔です)。このPodのスクレイプ間隔は15秒です。stepが15・108・600・3600秒のときのrateウィンドウをそれぞれ計算したあと、そのウィンドウで5xx率の式を同じstepで投げてください(範囲12時間)。/root/obs-graph-traps/rate-interval.tsvにヘッダーなしで4行、各行はタブで区切った4列<step초> <rate 창 초> <점 수> <최댓값>です(プレースホルダーはstepの秒数、rateウィンドウの秒数、点の数、最大値です)。そして/root/obs-graph-traps/04-note.txtに、reason=で始まる1行を40文字以上で書き、step 3600のときだけ、同じクエリを投げ直すたびに最大値が変わる理由を説明してください。

$__intervalは、パネルの時間範囲をピクセル幅で割った値なので、ここではstepがそのまま$__intervalです。108は12時間のパネルを400ピクセルで描くときに出る値で、3600ははるかに広い範囲を見るときに出ます。ウィンドウは、3615sのように秒単位の文字列でそのまま入れればよいのです。グリッドはstartから始まってstep間隔で置かれ、startは「今」に合わせて動きます。stepが事故の長さより長くなると、グリッド点がピークのどこに落ちるかが最大値を変えます。

比率の平均は、全体の比率ではない

同じ12時間の5xx率を、2通りで求めてください。1つは1分間隔の比率の単純平均avg_over_time((sum(rate(http_requests_total{job="shop-api",status="500"}[5m])) / sum(rate(http_requests_total{job="shop-api"}[5m])))[12h:1m])、もう1つはトラフィックで重みを付けた全体の比率sum(increase(http_requests_total{job="shop-api",status="500"}[12h])) / sum(increase(http_requests_total{job="shop-api"}[12h]))です。/root/obs-graph-traps/weighted.txtに3行を書いてください。avg_of_ratio=<소수 여섯 자리>、weighted_ratio=<소수 여섯 자리>、gap_pct=<소수 두 자리>です(プレースホルダーは小数第6位、小数第6位、小数第2位です)。gap_pctは(前の値 − 後の値) ÷ 後の値 × 100です。

2つの値は、promqですぐに投げて試せます。前の式は、トラフィックの少ない明け方の1分と混み合う昼の1分を同じ重みで数え、後ろの式は、リクエスト数の分だけ重みを与えます。事故が閑散とした時間に起きたなら、どちらが大きくなるかを先に考えてみてください。

長いウィンドウのp99は、最悪だったp99ではない

histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[창])))を、ウィンドウ5m・15m・12hで3回投げてください(step 60、範囲12時間。プレースホルダーはウィンドウです)。/root/obs-graph-traps/p99-window.tsvにヘッダーなしで3行、各行はタブで区切った2列<창> <12시간 최댓값>です(プレースホルダーはウィンドウと12時間の最大値です)。そして/root/obs-graph-traps/06-note.txtに、reason=で始まる1行を40文字以上で書き、長いウィンドウの分位数が、短いウィンドウの分位数の最大値より小さくなる理由を説明してください。

ヒストグラムのバケットもカウンターなので、rateのウィンドウがそのまま適用されます。ウィンドウが長いと、遅いリクエストと速いリクエストが1つの桶に混ざり、裾が薄められます。そのため「過去12時間のp99」というパネル1つでは、「最悪のときにいくらだったか」に答えられません。しきい値はデフォルトのままで構いません。このステップで見るのは、最大値だけです。

応用①: 件数パネルが小数点を表示する

/opt/lab/graph/panels.ymlのパネルp1は「過去12時間の5xx件数」を尋ねていますが、小数点が付いた値を出します。/root/obs-graph-traps/fix-count.promqlに、同じ12時間の5xx件数を整数で出すPromQLを書いてください(コメントの#は残してもかまいません)。そして/root/obs-graph-traps/count-panel.txtに2行を書いてください。broken_value=<소수 세 자리>はp1の式が今出す値、fixed_value=<정수>は直したクエリが出す値です(プレースホルダーは小数第3位と整数です)。採点ツールは、直したクエリを実際に投げて値を確認します。

increase()とrate()は、サンプルがウィンドウの境界にぴったり置かれないため、両端で外挿します。そのため、整数であるべき件数が小数で出ます。正確な件数がほしければ、外挿しない方法で尋ねる必要があります。カウンターの現在の値と12時間前の値の差を考えてみてください。offset修飾子が役に立ちます。

応用②: エラー率とp99のパネルを直して提出する

/opt/lab/graph/panels.ymlのパネルp2(エラー率)とp3(p99レイテンシ)は、それぞれ「最も高かったときにいくらだったか」に答えられません。/root/obs-graph-traps/fix-ratio.promqlと/root/obs-graph-traps/fix-p99.promqlに、直したクエリを書いてください。そして/root/obs-graph-traps/verdict.tsvにヘッダーなしで2行、各行はタブで区切った4列<패널id> <고치기 전 12시간 최댓값> <고친 뒤 12시간 최댓값> <무엇이 틀렸었나>です(プレースホルダーはパネルid、修正前の12時間の最大値、修正後の12時間の最大値、何が間違っていたかです)。1行目はp2、2行目はp3で、最大値はstep 60・範囲12時間で測り、小数第6位までです。4列目は20文字以上で、数字を1つ以上含める必要があります。

p2は、式が間違っているのではなく、ウィンドウが長すぎます。p3は2か所が間違っています。ウィンドウも長く、すでに出た分位数を平均しています。ヒストグラムは、バケットを先にleごとに合算してから、分位数を求める必要があります。直した値は、ステップ3・6ですでに見た数字と同じであれば正常です。