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

可観測性

ラベルを一つ足したら時系列が 100 倍になった

TT Labで続きを見る

目標

今、時系列が何にどれだけ使われているかを測り、ラベルを足したときにいくつになるかを先に予測してから、実際に起動して確認し、バジェットの上限を検査スクリプトで固定して、何を残し何を捨てるかを決めます。

なぜ重要なのか

カーディナリティは、事故が起きる前には誰も見ないコストです。ラベルを1つ足すのはコード1行ですが、そのラベルの値がユーザー数の分だけ増えると、時系列は積で増え、ストレージとメモリとクエリ時間がそろって増えます。しかもそのコストは「遅くなった」という形で現れるので、原因を探すのが難しくなります。そこで、2つを習慣にする必要があります。ラベルを足す前に、組み合わせの数を先に掛け算してみること、そしてメトリクスごとの時系列の上限を検査で固定して、人ではなくパイプラインが止めるようにすることです。何を捨てるかを決めるときの基準は「このラベルで何を決定するのか」で、捨てた軸が答えていた質問を、ログやトレースが代わりに答えられるかも一緒に確認しなければなりません。

ステップ

  1. /root/obs-cardinality-budget/inventory.tsvを作成してください。job="shop-api"が出力するメトリクスごとに、時系列がいくつあるかを<지표이름> <시계열 수>の2列で(プレースホルダーはメトリクス名と時系列数です)、多い順にすべて書きます(5行です)。Pod全体ではなくこのジョブ1つに絞る理由は、Prometheus自身が出力するメトリクスがラボの途中でも増え続け、基準が揺らぐからです。
  2. /root/obs-cardinality-budget/labels.tsvを作成してください。3行で、各行は<라벨이름> <서로 다른 값의 수>です(プレースホルダーはラベル名と異なる値の数です)。leとhandlerはヒストグラムのメトリクスhttp_request_duration_seconds_bucketで、statusはカウンターhttp_requests_totalで数えます(どちらもjobはshop-api)。多い順に書いてください。そして/root/obs-cardinality-budget/02-note.txtに、top_label=<1위 라벨 이름>とproduct=<le 값 수 × handler 값 수>の2行を書いてください(プレースホルダーは、1位のラベル名と、leの値の数×handlerの値の数です)。
  3. /root/obs-cardinality-budget/cost.txtに3行を書いてください。series=はjob="shop-api"の時系列数、samples_per_day=はその時系列が1日に残すサンプル数、bytes_per_day=はそのサンプルが占めるバイト数です。このPodのスクレイプ間隔は15秒で、サンプル1つは平均2バイトとします(Prometheusのストレージのドキュメントが述べる1–2バイトのうち、保守的な側です)。3つの値は、互いに掛け算で辻褄が合う必要があります。
  4. 新しいメトリクスcheckout_requests_totalを出力しようとしています。ラベルは、region(ap1・ap2・us1・eu1)、tier(free・pro・team)、endpoint(cart・pay・refund・ship・track)の3つです。/root/obs-cardinality-budget/forecast.txtに、formula=とpredicted_series=の2行を書いてください。formulaは掛け算の式(例: 2*3*4)、predicted_seriesはその結果です。まだ何も起動しないでください。
  5. /root/obs-cardinality-budget/exporter.pyを作成してください。引数に--printを渡すとエクスポジション形式のテキストを標準出力に出力して終了し、引数がなければ127.0.0.1:9102で/metricsを提供します。ステップ4の3つのラベルの組み合わせごとに、checkout_requests_totalを1行出力します。環境変数USERSが0より大きければ、user_idラベルが付きます。そのあと/etc/prometheus/prometheus.ymlにcardinality-labというスクレイプジョブを追加して、設定を再読み込みさせてください。最初のスクレイプが終わったら、/root/obs-cardinality-budget/measured.txtにmeasured_series=<수>を1行書きます(プレースホルダーは数です)。
  6. USERS=100でexporterを起動し直して、user_idラベルを付けてください。/root/obs-cardinality-budget/explode.tsvに2行を書きます。各行はタブで区切った3列<이름> <시계열 수> <하루 바이트>で(プレースホルダーは名前、時系列数、1日あたりのバイト数です)、1行目の名前はbefore(user_idなし)、2行目はafter(user_idあり)です。1日あたりのバイト数は、ステップ3と同じ方法(時系列×5760×2)で計算します。
  7. /root/obs-cardinality-budget/budget.shを作成してください。bash budget.sh <상한표>で呼び出すと(プレースホルダーは上限表です)、上限表の各行(<지표이름> <최대 시계열 수>、プレースホルダーはメトリクス名と最大時系列数です)ごとに現在の時系列数を数えて、<지표이름> <지금> <상한> OKまたは... OVERを1行ずつ出力します(プレースホルダーはメトリクス名、現在の値、上限です)。1つでも上限を超えたら終了コード1、超えなければ0で終わります。そして/root/obs-cardinality-budget/caps.tsvにcheckout_requests_totalの上限を500として書き、その表で1回実行して、出力を/root/obs-cardinality-budget/gate-out.txtに保存してください。
  8. 時系列の上限が500です。/root/obs-cardinality-budget/decision.txtに4行を書いてください。keep=には残すラベル名をカンマ区切りで(例: region,tier)、drop=には捨てるラベル名をカンマ区切りで、projected_series=には残したラベルだけで計算した時系列数を、lost_question=には、そのラベルを捨てたとき、もう答えられなくなる質問を30文字以上で書きます。ラベルの値の個数は、region 4・tier 3・endpoint 5・user_id 100です。

参考

1つのサービスの時系列が何に使われているかを数える

/root/obs-cardinality-budget/inventory.tsvを作成してください。job="shop-api"が出力するメトリクスごとに、時系列がいくつあるかを<지표이름> <시계열 수>の2列で(プレースホルダーはメトリクス名と時系列数です)、多い順にすべて書きます(5行です)。Pod全体ではなくこのジョブ1つに絞る理由は、Prometheus自身が出力するメトリクスがラボの途中でも増え続け、基準が揺らぐからです。

count by (__name__)(last_over_time({job="shop-api"}[1m]))を投げると、メトリクスごとの時系列数が出ます。ヒストグラム1つがバケットの数だけ時系列を作ることが、すぐ見えるはずです。

どのラベルが値を多く作るか

/root/obs-cardinality-budget/labels.tsvを作成してください。3行で、各行は<라벨이름> <서로 다른 값의 수>です(プレースホルダーはラベル名と異なる値の数です)。leとhandlerはヒストグラムのメトリクスhttp_request_duration_seconds_bucketで、statusはカウンターhttp_requests_totalで数えます(どちらもjobはshop-api)。多い順に書いてください。そして/root/obs-cardinality-budget/02-note.txtに、top_label=<1위 라벨 이름>とproduct=<le 값 수 × handler 값 수>の2行を書いてください(プレースホルダーは、1位のラベル名と、leの値の数×handlerの値の数です)。

あるラベルの値がいくつあるかは、count(count by (<라벨>) (...))で数えます(プレースホルダーはラベルです)。今生きている時系列だけを見るには、last_over_time(<선택자>[1m])で囲んでください(プレースホルダーはセレクターです)。そうしないと、バックフィルしてある過去の時系列まで一緒に数えてしまい、値が揺らぎます。最後の積は、ステップ1で見たヒストグラムの時系列数と一致するはずです。

このサービスのメトリクスは1日にどれだけ使うか

/root/obs-cardinality-budget/cost.txtに3行を書いてください。series=はjob="shop-api"の時系列数、samples_per_day=はその時系列が1日に残すサンプル数、bytes_per_day=はそのサンプルが占めるバイト数です。このPodのスクレイプ間隔は15秒で、サンプル1つは平均2バイトとします(Prometheusのストレージのドキュメントが述べる1–2バイトのうち、保守的な側です)。3つの値は、互いに掛け算で辻褄が合う必要があります。

時系列数はcount(last_over_time({job="shop-api"}[1m]))です。1日は86400秒なので、15秒間隔なら時系列あたりのサンプル数が決まります。この数字が、そのまま「ラベルを1つ足すときに増えるコスト」の単価です。

測る前に、先に予測する

新しいメトリクスcheckout_requests_totalを出力しようとしています。ラベルは、region(ap1・ap2・us1・eu1)、tier(free・pro・team)、endpoint(cart・pay・refund・ship・track)の3つです。/root/obs-cardinality-budget/forecast.txtに、formula=とpredicted_series=の2行を書いてください。formulaは掛け算の式(例: 2*3*4)、predicted_seriesはその結果です。まだ何も起動しないでください。

ラベルの組み合わせの数が、そのまま時系列の数です。値が互いに独立なら、各ラベルの値の個数を掛けます。予測を先に書いておく理由は、測ったあとで数字を合わせると、何を誤って考えていたのかがわからなくなるからです。

exporterを起動して予測を確認する

/root/obs-cardinality-budget/exporter.pyを作成してください。引数に--printを渡すとエクスポジション形式のテキストを標準出力に出力して終了し、引数がなければ127.0.0.1:9102で/metricsを提供します。ステップ4の3つのラベルの組み合わせごとに、checkout_requests_totalを1行出力します。環境変数USERSが0より大きければ、user_idラベルが付きます。そのあと/etc/prometheus/prometheus.ymlにcardinality-labというスクレイプジョブを追加して、設定を再読み込みさせてください。最初のスクレイプが終わったら、/root/obs-cardinality-budget/measured.txtにmeasured_series=<수>を1行書きます(プレースホルダーは数です)。

サーバーはhttp.serverのThreadingHTTPServerで十分です。エクスポジション形式は、1行に이름{라벨="값",...} 값です(プレースホルダーは、名前、ラベル名、ラベルの値、サンプルの値です)。設定の反映はcurl -X POST http://127.0.0.1:9090/-/reloadで、スクレイプ間隔が15秒なので少し待つ必要があります。時系列の数はcount(checkout_requests_total)で数えます。

ラベル1つで、時系列が100倍になる

USERS=100でexporterを起動し直して、user_idラベルを付けてください。/root/obs-cardinality-budget/explode.tsvに2行を書きます。各行はタブで区切った3列<이름> <시계열 수> <하루 바이트>で(プレースホルダーは名前、時系列数、1日あたりのバイト数です)、1行目の名前はbefore(user_idなし)、2行目はafter(user_idあり)です。1日あたりのバイト数は、ステップ3と同じ方法(時系列×5760×2)で計算します。

時系列数は、exporterを--printで実行してcheckout_requests_totalで始まる行を数えればすぐに出ます。サーバーが起動していなくてもかまいません。2行の差が、そのまま「ラベル1つの値」です。

上限をコードで固定する

/root/obs-cardinality-budget/budget.shを作成してください。bash budget.sh <상한표>で呼び出すと(プレースホルダーは上限表です)、上限表の各行(<지표이름> <최대 시계열 수>、プレースホルダーはメトリクス名と最大時系列数です)ごとに現在の時系列数を数えて、<지표이름> <지금> <상한> OKまたは... OVERを1行ずつ出力します(プレースホルダーはメトリクス名、現在の値、上限です)。1つでも上限を超えたら終了コード1、超えなければ0で終わります。そして/root/obs-cardinality-budget/caps.tsvにcheckout_requests_totalの上限を500として書き、その表で1回実行して、出力を/root/obs-cardinality-budget/gate-out.txtに保存してください。

時系列数はcount(<지표>)で数えます(プレースホルダーはメトリクスです)。結果がなければ0と見なす必要があります。jqの//でデフォルト値を与えられます。終了コードは最後に1回だけ出す必要があるので、変数にためておいてください。採点ツールは、自分で作った2つの上限表でこのスクリプトを実行し、成功と失敗の両方を確認します。

バジェットに収めるには、何を捨てるか

時系列の上限が500です。/root/obs-cardinality-budget/decision.txtに4行を書いてください。keep=には残すラベル名をカンマ区切りで(例: region,tier)、drop=には捨てるラベル名をカンマ区切りで、projected_series=には残したラベルだけで計算した時系列数を、lost_question=には、そのラベルを捨てたとき、もう答えられなくなる質問を30文字以上で書きます。ラベルの値の個数は、region 4・tier 3・endpoint 5・user_id 100です。

捨てるラベルを選ぶ基準は、「このラベルで何を決定するのか」です。決定を変えないラベルは、高くつきます。個人単位の調査は、ログやトレースが答えられる質問なので、メトリクスからその軸を捨てても、調査能力を丸ごと失うわけではありません。