Dev Study
← SLI を数える — 分母と分子を決める
レッスン 8 / 10·約 8 分·第28期 SLI を数える

比は平均しない — 分子と分母を別々に足してから割る

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口今ここ
  6. アプリ処理今ここ
  7. DB保存

分母と分子の定義を変えずに、集計の順序と窓だけを変えると数字がどう動くかを見ます。

まず結論

同じ 9,096 行のアクセスログでも、3台の可用性を平均すると 76.541%、良い件数と有効な件数を別々に足してから割ると 99.549% になります。

1分窓で区切ると、窓の比の平均 74.695%・良い窓の割合(windows-based)50.000%・合算 99.549% の3つが出ます。どれを採るかは、何を1票と数えるかの決定です。

? 読む前に予測

3台の可用性が 8966/9000・87/90・2/6 でした。3台の比の平均と、良い件数と有効な件数を足してから割った値はどうなるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

1台に1票か、1要求に1票か

この回で決めることは、集計の順序と窓です。比を先に出して平均するか、良い件数と有効な件数を別々に足してから割るか、時間の窓ごとに良し悪しを付けて窓を数えるか。良い事象(5xx 以外)と有効な事象(全件)の定義は1行も変えません。それでも数字は変わります。

比の平均は、単位ごとに1票を配ります。9,000件の台も6件の台も同じ重みです。足してから割ると1要求が1票になり、台の重みは件数に比例します。Prometheus の practices ページ(Recording rules)はこう書きます。

When aggregating up ratios, aggregate up the numerator and denominator separately and then divide.

Do not take the average of a ratio or average of an average, as that is not statistically valid.

原文は条件を付けずに Do not と書いています。ここから先は本教材の整理です。比の平均を「全体の比」として読むと原文の指摘に当たります。一方で比の平均は「典型的な1台の成績」という別の問いには答える数で、全体の比の代わりにはならない、と読んでおきます。

同じ 9,096 行から、1票の単位を変えると別の数が出る(本回の実測)
同じ 9,096 行良い=5xx 以外、有効=全件
何を1票と数えるか
台ごとの比の平均1台=1票 → 76.541%
足してから割る1要求=1票 → 99.549%
良い窓の割合1窓=1票 → 50.000%
答えている問い
典型的な1台の成績
要求のうち成功した割合
良かった時間の割合
  • ・良い窓の割合は、窓の良さを good/valid ≥ 99% と本実験で決めたときの値
  • ・窓の比の平均(74.695%)は良い窓の割合とは別の数
具体実物

3台・9,096行のアクセスログ

Docker 28.4.0(linux/arm64)上の nginx:1.30.4-alpine(nginx/1.30.4)が、python:3.14-slim(Python 3.14.7)の自前アプリ3台へ振り分けます。アプリは ?fail=1 の要求だけ 500 を返し、乱数は使いません。件数は固定で、2回走らせて比と件数はすべて同じでした(2026-09-28)。

nginx.conf(抜粋。コメントと他の行は省略)設定ファイル
log_format sli '$msec $status $upstream_addr $request_uri';
access_log /var/log/nginx/sli-access.log sli;
location /a/ { proxy_pass http://sli-l8-app-a:8000/; }
location /b/ { proxy_pass http://sli-l8-app-b:8000/; }
location /c/ { proxy_pass http://sli-l8-app-c:8000/; }

ログを別名にしたのは、公式 nginx イメージの /var/log/nginx/access.log が /dev/stdout への symlink で、docker exec … cat が戻ってこなかったからです(1回目は docker logs から回収)。

集計は python3 の Fraction で厳密に行います。

$ python3 analyze.py access-run1.log の (1) 部分(総行数の行と見出し行を省略。run2 も同値)コマンド出力
inst good valid bad good/valid upstream
a 8966 9000 34 99.622% 172.21.0.2:8000
b 87 90 3 96.667% 172.21.0.3:8000
c 2 6 4 33.333% 172.21.0.4:8000
mean of 3 instance ratios = 10333/13500 = 76.541%
pooled sum(good)/sum(valid) = 9055/9096 = 99.549%
difference (pooled - mean) = 23.009 points

c 台は6件中4件が失敗して 33.333%。平均ではこれが3分の1の重みを持ち、合算では 6/9096、約 0.066% です。件数の少ない単位の比が極端に振れる現象は、SRE Workbook 第2章が顧客ごとの比について書いています(括弧内の理由を省略)。

customers who send a very low number of requests will have either 100% availability ... or very low availability

顧客を台に置き換えたのは本教材の敷衍です。

✓ ここまでの確認

3台の比の平均 76.541% が、合算の 99.549% より大きく低くなった主な理由はどれですか。

抽象原理

時間で区切っても、同じ選択が出てくる

台ではなく時間で区切っても同じ選択が出ます。Google Cloud の概念ページは SLI を2種に分けます。

Request-based SLIs, where good service is measured by counting atomic units of service, like the number of successful HTTP requests.

Windows-based SLIs, where good service is measured by counting the number of time periods, or windows, during which performance meets a goodness criterion, like response latency below a given threshold.

request-based は足してから割る側です。windows-based は窓ごとに良い/悪いの2値を付けてから、良い窓の数を窓の数で割ります。窓ごとの比を平均する計算ではありません。比の平均と共通するのは「1窓が1票で、窓の中の件数が重みにならない」という性質だけで、同じページがそれを書いています。

A measurement interval that failed every one of its calls counts against the SLO as much as a measurement interval that had one error too many. Also, intervals with a low number of calls count against the SLO as much as a measurement interval with heavy activity.

ページは優劣を付けていません。request-based は what percentage of work your service did properly、windows-based は what percentage of the time your customers found the service to be working well or poorly に答える、と書き分けます。If the raw metric available to you is a latency percentile, then you must use a windows-based SLO. ともあり、windows-based は誤りではありません。「比を平均しない」の逐語は Prometheus のもので、Google Cloud の概念ページには平均を禁じる文は見当たりません(確認範囲: 同ページ全文)。

具体実物

1分窓×4 — 4件全滅の窓と、1件多かった窓

同じ構成で、壁時計の1分ごとに件数を変えて送りました。窓は $msec(ログ書き込み時刻)を60で割った整数部です。窓の良さは good/valid が 99% 以上、と本実験で決めた基準で、原典の値ではありません。要求が0件の窓は出ないように組んでいます。

$ python3 analyze.py access-run1.log の (2) 部分(見出し行と BAD windows 行を省略)コマンド出力
minute_epoch good valid bad good/valid allowed_bad window
29843407 3024 3030 6 99.802% 30 GOOD
29843408 2999 3030 31 98.977% 30 BAD
29843409 0 4 4 0.000% 0 BAD
29843410 3032 3032 0 100.000% 30 GOOD
(a) mean of 4 window ratios = 74.695%
(b) windows-based: good windows/total = 2/4 = 50.000%
(c) pooled sum(good)/sum(valid) = 9055/9096 = 99.549%

同じログから3つの数が出ます。悪い窓は、静かな1分に来た4件が全部失敗した窓と、3,030件中31件が失敗した窓(許容は30件で、1件だけ多い)。windows-based ではどちらも悪い窓1つ、つまり 1/4 の重みです。合算では前者は 4/9096、約 0.044% にすぎません。

2つ目の窓(29843408)の 500 を1行だけ取り除いて再計算すると、こうなります。

whatif-run1.txt(抜粋。見出し行・29843408 以外の窓の行・BAD windows 行を省略)コマンド出力
removed one line: 1790604483.014 500 172.21.0.2:8000 /a/?fail=1&n=2
lines: 9096 -> 9095
29843408 2999 3029 30 99.010% 30 GOOD
(a) mean of 4 window ratios = 74.703%
(b) windows-based: good windows/total = 3/4 = 75.000%
(c) pooled sum(good)/sum(valid) = 9055/9095 = 99.560%

1件の差で windows-based は 50% から 75% へ動き、合算は 0.011 ポイントしか動きません。

時間を1件と数える読みは身近です。クイズ observability-slo-6 の「99.9% は月約43分」は、30日=43,200分の 0.1% で 43.2分。時間単位の読みとして正しい計算です。Google Cloud の例も1分窓・30日・99% で 42,768 “good” intervals out of 43,200 minutes と書きます。一方 request-based の 0.1% は失敗してよい要求の件数で、分ではありません。どちらも正しく、分母が違います。第15期「同じ事実から、複数の正しい報告が作れる」は、リクエスト分母と時間分母の比が同じ数になる理由はないとしつつ、値の計算はしませんでした。ここに並んだ 50.000% と 99.549% がその計算です。

✓ ここまでの確認

1分窓×4の実演で windows-based が 50.000% になりました。この値について正しいのはどれですか。

⇄ 2つを見比べる
具体例 A

台で区切る(インスタンス方向)

3台の比の平均は 76.541%、足してから割ると 99.549%。6件の c 台(33.333%)が、平均では3分の1の重みを持ちます。

合成の仕方と1票の単位表
合成の仕方 1票 値
台ごとの比の平均 1台 76.541%
足してから割る 1要求 99.549%
具体例 B

時間で区切る(1分窓×4)

窓の比の平均 74.695%、良い窓の割合 50.000%、合算 99.549%。4件だけの窓も3,030件の窓も、前の2つでは同じ1票です。

合成の仕方と1票の単位表
合成の仕方 1票 値
窓の比の平均 1窓 74.695%
良い窓の割合 1窓(0/1) 50.000%
足してから割る 1要求 99.549%

台で区切っても時間で区切っても、比の平均と合算が大きく離れました。共通する理由は何でしょうか。

件数の少ない単位(c 台、4件の窓)が、それぞれの合成で何票を持っていたかを数えてみてください。

この章のまとめ

  • この回で決めたのは集計の順序と窓。良い・有効の定義を変えなくても数字は変わる。
  • 同じ 9,096 行で、台の比の平均 76.541%、足してから割ると 99.549%。6件の台が平均では1票を持つ。
  • 1分窓×4では、窓の比の平均 74.695%、windows-based 2/4 = 50.000%、合算 99.549%。3つは別の数。
  • windows-based では、4件全滅の窓も31件失敗(許容30件)の窓も悪い窓1つ。1件減らすと 50%→75%、合算は 0.011 ポイント。
  • 「比を平均しない」の逐語は Prometheus。Google Cloud は2種を並べ、百分位しか無いなら windows-based を must とする。
  • 「月約43分」は時間を1件と数える読みで正しい。request-based の 0.1% は件数。どちらも正しく、分母が違う。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

Prometheus の practices ページ(Recording rules)は、比を集約するときの手順をどう書いていますか。

用語集(3語)
Request-based SLI
良い要求の数を要求の総数で割る SLI。台や時間で区切っても、足してから割れば1要求が1票になる。
Windows-based SLI
窓ごとに良い/悪いを付け、良い窓の数を窓の数で割る SLI。窓の比の平均とは別の数。1窓が1票で、窓の中の件数は重みにならない。
窓の良さの基準
その窓を良いと数える条件。本回の実演では good/valid が 99% 以上を選んだ。原典の値ではない。