- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口今ここ
- アプリ処理今ここ
- DB保存
値は測りますが、何を1件と数えるかは決めます。今回決めるのは単位です。
まず結論
クライアントが最大3回まで試す100操作の実験で、同じ nginx ログが、試行で数えると 69.23%、操作で数えると 99.00%、初回の試行だけなら 66.00% になりました。
リトライで成功率が良くなるかどうかは、1件を試行と操作のどちらに取るかを言わないと決まりません。nginx の1行は、利用者に返した応答(`$status`)と upstream への試行(`$upstream_status`)という2つの単位を並べて持っています。
100操作を、失敗したら最大3回まで試すクライアントで流しました。最終的に成功を受け取ったのは99操作です。同じ実験の nginx アクセスログを「1行=1件」で数えた成功率は、どれに近いでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
この回で決めること — 1件は試行か、操作か
この回で決めるのは単位です。 1件を何と取るかには少なくとも3通りあります。利用者に返した1応答(nginx の $status)、upstream への1試行($upstream_status の各値)、クライアントの1操作(再送をまとめたもの)。リトライが無ければ3つは一致します。リトライがあると、同じログから3つの違う数字が出ます。
原典も数え分けています。SRE Book 第22章は 3 retries (4 attempts) と書き、第21章は図の説明に corresponding to 1,000 initial requests, not counting retries と断っています。
第3回「原因ではなく、返ったもので数える」でログの列として出した $upstream_status を、ここで定義します。
keeps status code of the response obtained from the upstream server. Status codes of several responses are separated by commas and colons like addresses in the $upstream_addr variable.
nginx が次の server に回すと、1行の中に試行が複数並びます。 どの失敗で回すかは proxy_next_upstream が決め、既定は error timeout です。502 を回すには http_502 を書きます。
- ・利用者は 200 だけを受け取る。502 は $upstream_status にしか残らない
- ・$upstream_status は第3回でログの列として出した値。定義はこの回で扱う
同じ6行が、6/6 にも 6/9 にもなる
1つの nginx コンテナの中に、常に 200 を返す 9001 と、常に 502 を返す 9002 を置きました(環境: Docker 28.4.0 arm64 / nginx:1.30.4-alpine / 2026-09-28)。
log_format sli '$request_method $request_uri status=$status upstream_status="$upstream_status" upstream_addr="$upstream_addr" op=$http_x_op_id att=$http_x_attempt';server { listen 9001; access_log off; location / { return 200 "ok from 9001\n"; } }server { listen 9002; access_log off; location / { return 502 "bad from 9002\n"; } }upstream pair_default {server 127.0.0.1:9001;server 127.0.0.1:9002;}upstream pair_nofails {server 127.0.0.1:9001 max_fails=0;server 127.0.0.1:9002 max_fails=0;}# 8080: proxy_pass http://pair_default; proxy_next_upstream error timeout http_502;# 8081: proxy_pass http://pair_nofails;# 8082: proxy_pass http://pair_nofails; proxy_next_upstream error timeout http_502;
前段の3つに GET を6回ずつ、8082 の設定に POST を4回送り、nginx のログを $status と $upstream_status で数えました。
== (a) a-8080: 6 linesby $status : 200=6 non200=0 -> 6/6 = 1.0000by $upstream_status : 200=6 non200=1 -> 6/7 = 0.8571== (a) a-8081: 6 linesby $status : 200=3 non200=3 -> 3/6 = 0.5000by $upstream_status : 200=3 non200=3 -> 3/6 = 0.5000== (a) a-8082: 6 linesby $status : 200=6 non200=0 -> 6/6 = 1.0000by $upstream_status : 200=6 non200=3 -> 6/9 = 0.6667== (a) post-8082: 4 linesby $status : 200=2 non200=2 -> 2/4 = 0.5000by $upstream_status : 200=2 non200=2 -> 2/4 = 0.5000
8082 は偶数番の3行が upstream_status="502, 200" で、$status で 6/6、$upstream_status で 6/9 です。8081 は http_502 を書いていないので 502 がそのまま利用者に返り、どちらで数えても 3/6。POST は non_idempotent を書いていないので次へ回らず、2/4 です。
8082(`proxy_next_upstream` に `http_502`、`max_fails=0`)へ GET を6回送り、利用者は6回とも 200 を受け取りました。`$upstream_status` の各値を1件と数えた成功率はどれですか。
クライアントの再送は、サーバには1行ずつ届く
nginx の中の再試行は1行に畳まれました。クライアントの再送は違います。1試行が1つの要求として届き、1行ずつ書かれます。
SRE Book 第21章は From the point of view of our load balancing policies, retries of requests are indistinguishable from new requests. と書きます。主語は Google の負荷分散の方針で、サーバ一般の話ではありません。同じ章は区別させる方式も書いています。
A third approach has clients include a counter of how many times the request has already been tried in the request metadata. For instance, the counter starts at 0 in the first attempt and is incremented on every retry until it reaches 2
試行番号と操作IDが要求に載っていれば、サーバ側のログからも操作単位を組み立て直せます。 載っていなければ、ログで数えられるのは試行だけです。
同じ143行が、69.23% にも 99.00% にもなる
nginx の 8090 の先に自前アプリ(python:3.12-slim、Python 3.12.14)を置きました。アプリは sha256("l5:op:attempt") を [0,1) に写し、0.3 未満なら 500 を返します。クライアントは100操作を送り、500 なら最大3試行まで再送し、X-Op-Id と X-Attempt: 0,1,2 を付けます。
---- client summary ----operations : total=100 ok=99 fail=1 availability(op)=99/100=0.9900attempts : total=143 200=99 500=44 availability(attempt)=99/143=0.6923attempts per op : 1x66 2x25 3x9first attempt only (= no retry): 200=66/100=0.6600== (b) /work nginx log, 1 line = 1 client attempt: 143 linesby $status : 200=99 non200=44 -> 99/143 = 0.6923by $upstream_status : 200=99 non200=44 -> 99/143 = 0.6923grouped by op header: ops=100 ok=99 fail=1 -> 99/100 = 0.9900ops with all attempts failed: ['86'] (attempt-unit lines they contribute: 3, op-unit: 1)500 lines total=44; of which from all-failed ops=3, from ops that later succeeded=41
- 試行単位: 良い=200 の行 99、全件=143 行 → 99÷143 = 69.23%
- 操作単位: 良い=成功した操作 99、全件=100 操作 → 99÷100 = 99.00%
- 初回の試行だけ: 66÷100 = 66.00%
分母の143は 66×1 + 25×2 + 9×3。500 の44行のうち41行は、利用者が最後には成功を受け取った操作の行です。 逆に、3回とも失敗した op=86 は、試行単位では3件、操作単位では1件です(run1.nginx.log L147-149)。
実演 (b) で、nginx のログだけから操作単位の 99/100 を出せたのはなぜですか。
nginx の次サーバ転送(8082)
利用者が受け取るのは 200 だけです。$status で数えると 6/6 で、502 は $upstream_status の中にしか残りません。応答の単位に比べて、分母は6から9になりました。
GET /a-8082 status=200 upstream_status="200" upstream_addr="127.0.0.1:9001" op=- att=-GET /a-8082 status=200 upstream_status="502, 200" upstream_addr="127.0.0.1:9002, 127.0.0.1:9001" op=- att=-
クライアントの再送(8090)
試行ごとに別の行が書かれます。行で数えると 99/143 で、操作の単位に比べて分母は100から143になりました。ヘッダで束ねれば 99/100 です。
GET /work status=500 upstream_status="500" upstream_addr="172.21.0.2:9000" op=86 att=0GET /work status=500 upstream_status="500" upstream_addr="172.21.0.2:9000" op=86 att=1GET /work status=500 upstream_status="500" upstream_addr="172.21.0.2:9000" op=86 att=2
片方では再試行が成功率を100%に見せ、もう片方では69%に見せました。共通する構造は何でしょうか。
それぞれで「1行」「1値」「1操作」がいくつあるかを数えてみてください。
この章のまとめ
- この回で決めるのは単位。1応答(
$status)、1試行($upstream_statusの各値)、1操作(ヘッダで束ねる)。 - 8082 の同じ6行で、
$statusなら 6/6、$upstream_statusなら 6/9。"502, 200"が 9002 に当たるたびに出たのはmax_fails=0の 8082(6行中3行)。既定のmax_fails=1の 8080 では最初の1回だけだった(6/7)。 - 既定の
proxy_next_upstreamはerror timeout。502 はhttp_502を書いたときだけ次へ回る。POST はnon_idempotentが無ければ回らない。 - 同じ143行が、試行単位で 69.23%、操作単位で 99.00%、初回だけで 66.00%。500 の44行のうち41行は、最後に成功した操作のもの。
- 試行番号と操作IDを要求に載せれば、サーバ側のログからも操作単位を組み立て直せる。載せなければ数えられるのは試行だけ。
- 原典も数え分けている。
3 retries (4 attempts)、3層なら 4^3 = 64 試行。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
SRE Book 第22章の例で、JavaScript・フロントエンド・バックエンドの3層がそれぞれ `3 retries (4 attempts)` をすると、利用者の1操作はデータベースに最大何試行を生みますか。
用語集(3語)
- 試行(attempt)と操作(operation)
- 操作は利用者の1回の意図、試行はそのために送った1回の要求。1操作は1〜複数試行になる。SRE Book 第22章は `3 retries (4 attempts)` と数え分ける。
- $upstream_status
- nginx が upstream から得た応答の状態コード。複数の server に当たった試行はカンマ区切りで並ぶ。`$status` は利用者に返した1応答の状態コード。
- proxy_next_upstream
- どの失敗で次の server に回すかを決める nginx の指令。既定は `error timeout`。`http_502` などは書いたときだけ対象になる。
出典(一次ソース)
- nginx — ngx_http_upstream_module($upstream_status L1388-1395、max_fails L217-224。nginx.org 現行文書、2026-09-28 取得。最新タグ release-1.31.6、実演は 1.30.4) ↗
- nginx — ngx_http_proxy_module(proxy_next_upstream の既定・non_idempotent・unsuccessful attempt。2026-09-28 取得) ↗
- SRE Book(web 版、© 2017)— Chapter 21. Handling Overload(retry budget、試行回数のメタデータ、1,000 initial requests) ↗
- SRE Book(web 版、© 2017)— Chapter 22. Addressing Cascading Failures(多層リトライは積、64 attempts) ↗
- SRE Workbook(© 2018)— Chapter 5. Alerting on SLOs(低トラフィック対策としてのクライアントのリトライ) ↗
- OpenTelemetry semantic-conventions — general/recording-errors.md(main コミット 094950f、2026-09-28 取得、Status: Development) ↗