このダッシュボードは1分に何回投げているのか
目標
ダッシュボード1つのクエリ回数・系列数・点の数を実際に測って値段を付け、バジェットをファイルと検査ツールで書いたうえで、そのバジェットに収まる版を作って提出します。
なぜ重要なのか
ダッシュボードはタダのように見えます。パネルを1つ追加する作業はクリック3回で、コストは別のチームのPrometheusで発生するからです。そのため、ダッシュボードはいつも、重くなる方向にしか育ちません。値段を付ける計算そのものは、難しくありません。パネルごとに付いているクエリを足し、一覧をサーバーに問い合わせる変数を足すと、1回開くときのクエリ数になり、ここに自動リフレッシュの間隔を掛けると、毎分のクエリ数になります。クエリ10個で10秒リフレッシュなら、1分に60回、1日に8万回で、その画面を誰も見ていない夜にも、同じように飛んでいきます。この数字を一度数えてみたチームと、数えてみていないチームでは、ダッシュボードへの向き合い方が変わります。
ステップ
lab-start-grafanaでGrafanaを起動し、/opt/lab/gfd/gfd-perf/heavy.jsonを、修正せず、そのままGrafanaにアップロードしてください(uidはファイルに書かれたgfd-perf、パネルは8つ)。curlで/api/dashboards/dbにPOSTすればよいです。このダッシュボードは、このラボが終わるまで原本のまま残しておきます。縮小した版は、最後のステップで別のuidとして別に保存します。- Grafanaにアップロードされたダッシュボードをダウンロードして、数えてください。
/root/gfd-perf/02-count.txtに、panels=、targets=、var_queries=、open=の4行を書きます。panelsは行(row)を除いたパネルの数、targetsはすべてのパネルのクエリの数、var_queriesはtemplating.listのうちtypeがqueryの変数の数、openはtargetsとvar_queriesを足した値で、ダッシュボードを1回開くときのクエリ数です。 - パネルごとに付いているクエリを実際に投げて、返ってくる系列の数を測ってください。過ぎた時刻を1つ選び(今より5分以上前、6時間以内)、すべてのクエリをその時刻の瞬間値として投げます。
/root/gfd-perf/03-series.txtに、at=、total=、top_id=、top_series=の4行を書きます。atは選んだ時刻のエポック秒、totalはダッシュボードのすべてのクエリが返す系列の数の合計、top_idは系列を最も多く返すパネルのid、top_seriesはそのパネルの系列の数です。いちばん重いパネルのグラフに、線が何本描かれるかを考えてみてください。 - 過ぎた区間を1つ選び(2時間以上、終わりは今より前)、パネル3のp95のクエリを、同じ区間でstepだけを変えて2回投げてください。1回は
step=15、もう1回はstep=300です。/root/gfd-perf/04-points.txtに、start=、end=、points_fine=、points_coarse=の4行を書きます。points_fineはstep 15のとき、points_coarseはstep 300のときに、系列1つに返ってきた点の数です。 - ダッシュボードJSONの自動リフレッシュ間隔を読んで、
/root/gfd-perf/05-rate.txtに、refresh=、refresh_sec=、targets=、per_min=の4行を書いてください。refreshはJSONに書かれた文字列そのまま、refresh_secはそれを秒に換算した整数、targetsはステップ2で数えたクエリの数、per_minはtargetsに60 / refresh_secを掛けた値です。このラボの計算では、自動リフレッシュはパネルのクエリだけを再び投げ、変数クエリは再び投げないものとして扱います。 - パネル3のp95のクエリは、バケット系列48個を読んで、毎回分位数を計算します。
/etc/prometheus/rules/gfd-perf.ymlに、グループ名gfd-perf、interval15秒、レコーディングルール1つを置いてください。recordはhandler:http_request_duration_seconds:p95、exprはパネル3のp95のクエリそのままです。promtool check rules /etc/prometheus/rules/gfd-perf.ymlで確認したあと、curl -X POST http://127.0.0.1:9090/-/reloadで反映し、20秒以上待ってから、promq 'handler:http_request_duration_seconds:p95'が値を返すかを確認して、次のステップに進んでください。 /root/gfd-perf/budget.txtに、バジェットを5行で書いてください。max_panels=6、max_targets=6、max_var_queries=0、min_refresh_sec=60、max_queries_per_min=6です。そして、/root/gfd-perf/budget.pyを作成してください。バジェットファイルのパスとダッシュボードJSONのパスを引数に受け取って、違反を1行に1つずつ(B1からB5までで始まる形で)出力し、違反が1つでもあれば、終了コード1で終わる必要があります。B1はパネル数の超過、B2はクエリ数の超過、B3は変数クエリ数の超過、B4はリフレッシュがオンなのに間隔が短すぎること、B5は毎分のクエリ数の超過です。原本の/opt/lab/gfd/gfd-perf/heavy.jsonに対して実行して、5つの違反がすべて検出されるかを確認してください。- 原本の
gfd-perfはそのままにして、バジェットに収まる新しいダッシュボードを、uidgfd-perf-slimで保存してください。減らし方は自由ですが、次の3つは必ず入れてください。どのパネルも使っていない変数クエリをなくす、自動リフレッシュ間隔を60秒以上に変える、そしてp95パネルのクエリを、ステップ6で作ったhandler:http_request_duration_seconds:p95に変える、の3つです。保存したあと、そのダッシュボードをそのままダウンロードして、/root/gfd-perf/fixed.jsonに保存し(.dashboardの本体だけ)、ステップ7の検査ツールを実行して、違反が0で、終了コードが0であることを確認してください。そして/root/gfd-perf/08-review.mdに、B1=からB5=までの5行で、各項目をどのようにバジェットに収めたかを、それぞれ30文字以上で書いてください。
参考
- Grafanaは
lab-start-grafanaで起動します(数十秒かかります)。Prometheusはすでに起動しています。 - 元のダッシュボードは
/opt/lab/gfd/gfd-perf/heavy.jsonにあります。このファイルは修正せず、読むだけにしてください。ステップ7でもう一度使います。 - ダッシュボードを保存するAPIは
POST /api/dashboards/dbで、本文は{"dashboard": ..., "overwrite": true}です。 - ルールの検査は
promtool check rules <파일>(プレースホルダーはファイル名です)、反映はcurl -X POST http://127.0.0.1:9090/-/reloadです。反映の直後には、値がまだありません。グループのintervalが1回過ぎて、初めて最初の計算が終わります。 - コストは個数だけで測ります。このPodでミリ秒を測ると、同じクエリでも測るたびに違う値が出ます。2つのCPUを学習者のプロセスと採点ツールが分け合って使っているからです。画面が実際にどれだけ速く表示されるかは、Webプレビューで目で見てください。
- よくある間違い①は、縮小した版を同じuidに上書きすることです。前のステップで数えた値が原本を指しているので、原本は残しておきます。
- よくある間違い②は、新しいuidで保存するときに、本文の
idを取り除かないことです。そうすると、既存のダッシュボードが変わってしまいます。 - ダッシュボードJSONモデル · Prometheusクエリエディター · PrometheusクエリAPI · レコーディングルールの設定 · レコーディングルールの名前の慣例
値段を付けるダッシュボードをアップロードする
lab-start-grafanaでGrafanaを起動し、/opt/lab/gfd/gfd-perf/heavy.jsonを、修正せず、そのままGrafanaにアップロードしてください(uidはファイルに書かれたgfd-perf、パネルは8つ)。curlで/api/dashboards/dbにPOSTすればよいです。このダッシュボードは、このラボが終わるまで原本のまま残しておきます。縮小した版は、最後のステップで別のuidとして別に保存します。
保存APIは、ダッシュボードの本体をdashboardキーの中に入れ、overwriteを一緒に送ります。ファイルからその形を作るには、jq -n --slurpfileが便利です。Grafanaが起動するのに数十秒かかるので、/api/healthが応答するかどうかを、先に見てください。
1回開くのに、クエリが何回飛ぶのか
Grafanaにアップロードされたダッシュボードをダウンロードして、数えてください。/root/gfd-perf/02-count.txtに、panels=、targets=、var_queries=、open=の4行を書きます。panelsは行(row)を除いたパネルの数、targetsはすべてのパネルのクエリの数、var_queriesはtemplating.listのうちtypeがqueryの変数の数、openはtargetsとvar_queriesを足した値で、ダッシュボードを1回開くときのクエリ数です。
行の中に畳まれているパネルも、パネルです。jqで展開するには、.panels[] | (., (.panels[]?))のあとに、typeがrowのものを除外すればよいです。クエリはパネルごとにtargets配列に入っていて、パネル1つに2つ以上あることもあります。変数は、一覧をサーバーに問い合わせるものだけを数えます。手で書いておいた定数の一覧は、クエリを作りません。
どのパネルがいちばん重いのか
パネルごとに付いているクエリを実際に投げて、返ってくる系列の数を測ってください。過ぎた時刻を1つ選び(今より5分以上前、6時間以内)、すべてのクエリをその時刻の瞬間値として投げます。/root/gfd-perf/03-series.txtに、at=、total=、top_id=、top_series=の4行を書きます。atは選んだ時刻のエポック秒、totalはダッシュボードのすべてのクエリが返す系列の数の合計、top_idは系列を最も多く返すパネルのid、top_seriesはそのパネルの系列の数です。いちばん重いパネルのグラフに、線が何本描かれるかを考えてみてください。
系列の数は、/api/v1/queryの応答のdata.resultの長さです。時刻を固定するには、time=を一緒に送ってください。そうすれば、あとで数え直しても同じ答えが出ます。クエリをダッシュボードJSONから取り出して、ループで回せば、手で書き写さなくて済みます。パネル1つにクエリが2つあるなら、その2つを足したものが、そのパネルの系列の数です。
解像度を変えると、返ってくる点が何倍になるのか
過ぎた区間を1つ選び(2時間以上、終わりは今より前)、パネル3のp95のクエリを、同じ区間でstepだけを変えて2回投げてください。1回はstep=15、もう1回はstep=300です。/root/gfd-perf/04-points.txtに、start=、end=、points_fine=、points_coarse=の4行を書きます。points_fineはstep 15のとき、points_coarseはstep 300のときに、系列1つに返ってきた点の数です。
区間クエリは/api/v1/query_rangeで、start・end・stepを一緒に送ります。返ってきたJSONのdata.result[0].valuesの長さが、系列1つの点の数です。2つの数字の比を出してみると、解像度がコストにそのまま掛け算されることがわかります。区間を過ぎた絶対時刻で固定しておけば、あとでもう一度測っても同じ値が出ます。
自動リフレッシュを掛けて、毎分のクエリ数を出す
ダッシュボードJSONの自動リフレッシュ間隔を読んで、/root/gfd-perf/05-rate.txtに、refresh=、refresh_sec=、targets=、per_min=の4行を書いてください。refreshはJSONに書かれた文字列そのまま、refresh_secはそれを秒に換算した整数、targetsはステップ2で数えたクエリの数、per_minはtargetsに60 / refresh_secを掛けた値です。このラボの計算では、自動リフレッシュはパネルのクエリだけを再び投げ、変数クエリは再び投げないものとして扱います。
自動リフレッシュ間隔は、ダッシュボードJSONの最上部のrefreshキーに、10s・1mのような文字列として入っています。末尾の文字が単位で、その前が数字です。この数字がなぜ重要なのかピンと来なければ、24を掛けて1日分を出してみてください。誰も見ていない夜にも、同じ数が飛んでいます。
重い計算をレコーディングルールに移す
パネル3のp95のクエリは、バケット系列48個を読んで、毎回分位数を計算します。/etc/prometheus/rules/gfd-perf.ymlに、グループ名gfd-perf、interval15秒、レコーディングルール1つを置いてください。recordはhandler:http_request_duration_seconds:p95、exprはパネル3のp95のクエリそのままです。promtool check rules /etc/prometheus/rules/gfd-perf.ymlで確認したあと、curl -X POST http://127.0.0.1:9090/-/reloadで反映し、20秒以上待ってから、promq 'handler:http_request_duration_seconds:p95'が値を返すかを確認して、次のステップに進んでください。
レコーディングルールの名前には慣例があり、수준:지표:연산の形で、コロンを2つ使います(プレースホルダーは順に、集計レベル、指標、演算です)。グループのintervalが計算間隔で、反映の直後には、まだ1度も計算していないので、その間隔が1回過ぎて、初めて値ができます。curl -s http://127.0.0.1:9090/api/v1/rules | jqで、ルールが読み込まれたかどうかを見られます。
バジェットを書き、そのバジェットを検査する道具を作る
/root/gfd-perf/budget.txtに、バジェットを5行で書いてください。max_panels=6、max_targets=6、max_var_queries=0、min_refresh_sec=60、max_queries_per_min=6です。そして、/root/gfd-perf/budget.pyを作成してください。バジェットファイルのパスとダッシュボードJSONのパスを引数に受け取って、違反を1行に1つずつ(B1からB5までで始まる形で)出力し、違反が1つでもあれば、終了コード1で終わる必要があります。B1はパネル数の超過、B2はクエリ数の超過、B3は変数クエリ数の超過、B4はリフレッシュがオンなのに間隔が短すぎること、B5は毎分のクエリ数の超過です。原本の/opt/lab/gfd/gfd-perf/heavy.jsonに対して実行して、5つの違反がすべて検出されるかを確認してください。
バジェットを文章で書いておいても、誰も守りません。動くコードで書いてください。行(row)の中に畳まれたパネルもパネルであること、ファイルが{"dashboard": ...}で包まれている場合もあることを、忘れないでください。refreshがないか、オフになっていれば、自動リフレッシュがないということなので、B4とB5は検出されてはいけません。
バジェットの中に収めて、新しい版として提出する
原本のgfd-perfはそのままにして、バジェットに収まる新しいダッシュボードを、uidgfd-perf-slimで保存してください。減らし方は自由ですが、次の3つは必ず入れてください。どのパネルも使っていない変数クエリをなくす、自動リフレッシュ間隔を60秒以上に変える、そしてp95パネルのクエリを、ステップ6で作ったhandler:http_request_duration_seconds:p95に変える、の3つです。保存したあと、そのダッシュボードをそのままダウンロードして、/root/gfd-perf/fixed.jsonに保存し(.dashboardの本体だけ)、ステップ7の検査ツールを実行して、違反が0で、終了コードが0であることを確認してください。そして/root/gfd-perf/08-review.mdに、B1=からB5=までの5行で、各項目をどのようにバジェットに収めたかを、それぞれ30文字以上で書いてください。
新しいuidで保存するときは、本文からidを取り除く必要があります。残しておくと、Grafanaがそのidの既存のダッシュボードを書き換えてしまいます。クエリ数を減らす方法は、パネルを削除することだけではありません。2つのクエリを1つの式にまとめられるパネルがないかを見てください。件数2つを別々に受け取って、目で割り算するパネルがそうです。系列を48個返すパネルは、その画面から読み取れるものがないのではないかを、先に問いかけてみてください。