- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口今ここ
- アプリ処理
- DB保存
第35週は「上流に聞かずに済ませる」話でした。今回は「多すぎる要求をその場で止める」話です。どちらも上流を守る仕組みです。
まず結論
レート制限は「**持続レート**」と「**短期のバースト許容**」の2つの数で表せます。この形はトークンバケットの `rT+b` とも、TCPの輻輳ウィンドウとも同じです。
ただし**呼び名と出典には注意が要ります。** nginxは自ら「leaky bucket」と呼びますが、nginx自身はこの語を定義していません。RFC 3290 付録Aなどに説明はあるものの、nginxがどの定義に従うかは書かれていません。拒否コードも429とは限らず、**nginxの既定は503**です。
nginx の `limit_req` がリクエストを拒否するとき、既定で返すステータスコードはどれでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
2つの数で表せる
レート制限は、突き詰めると2つの数です。長期に維持できるレートと、短期にどれだけはみ出してよいか。
RFC 2216 はトークンバケットをこう定義しています。トークンレート r とバケットサイズ b があり、あらゆる時間区間 T について、送ったデータ量が rT + b を超えてはならない。r が持続レート、b が短期の超過許容量です。
持続レート 短期のバースト許容RFC 2216 r bnginx rate= burst= (既定 0)TCP(第24週) cwnd の上限 初期ウィンドウ
重要なのは、はみ出した分をどう扱うかは、この定義に含まれないことです。RFC 2216 は不適合パケットへの処置を「この定義では規定しない」と明言し、破棄・印付けなどを例示するにとどめています。捨てるか、遅らせるかは設計の選択です。
nginx で `limit_req zone=one;` とだけ書いた(burst を指定しなかった)場合、レートを超えたリクエストはどうなりますか。
nginxは2段構えで止める
nginx の limit_req は、その選択として遅延を先に置いています。公式の記述はこうです。レートを超えると「定められたレートで処理されるように遅延させられる」。そして「超過リクエストは、その数が最大バーストサイズを超えるまで遅延させられ」、超えた場合に「エラーで終了」する。
つまり 遅延 → 拒否 の2段です。ただし burst の既定は 0 なので、何も指定しなければ1段目が存在せず、超えた瞬間に拒否されます。
`limit_req zone=one burst=5 nodelay;` の `nodelay` は何をしますか。
TCPの輻輳ウィンドウ(第24週)
送信側は網がどれだけ詰まっているかを知りません。パケット損失や重複ACKという間接的な兆候から推し量り、cwnd を自分で増減させます。cwnd は送信側のローカル変数で、パケットには現れません。
上限の在り処 送信側のローカル変数(cwnd)誰が決める 送信側が推定する超過の合図 パケット損失・重複ACK3回超過時の挙動 cwnd を減らす(自律調整)
HTTPのレート制限(第36週)
サーバは `rate=` という設定値として上限を持っています。共有メモリゾーンに各キーの状態を置き、超えたリクエストをその場で遅らせるか拒否します。クライアントが上限を知るのは、拒否された後です。
上限の在り処 サーバの設定値と共有メモリゾーン誰が決める サーバが明示する(rate=)超過の合図 429、または nginx 既定の 503超過時の挙動 遅延(burst枠内)→ 拒否(枠超過)
どちらも「持続レートと短期のバースト」で送る量を抑えます。では、なぜTCPの輻輳制御は難しく、レート制限は素直に実装できるのでしょうか。
「上限の値を、誰が知っているか」を比べてください。
この章のまとめ
- レート制限は「持続レート」と「短期のバースト許容」の2つ。RFC 2216 の
rT+b、nginx のrate=/burst=、TCPの cwnd/初期ウィンドウは同じ形。 - はみ出した分の扱いは定義に含まれない。 捨てるか遅らせるかは設計の選択(RFC 2216)。
- nginxは 遅延 → 拒否 の2段構え。ただし
burstの既定は0なので、既定では超えた瞬間に拒否。nodelayは枠内の待ちを省くだけ。 - トークンバケットにもリーキーバケットにもRFCの説明がある(RFC 2216/2697、RFC 3290 付録A。いずれも Informational)。ただしnginxはどれに従うかを書いていないので、自称する呼び名から中身を推論しない。
- 429 は RFC 6585 §4、
Retry-Afterは RFC 9110 §10.2.3 と出典が別。Retry-Afterは MAY。 - nginx の拒否コードの既定は 503。429にするには
limit_req_status 429;が要る。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
「リーキーバケットはRFCで定義されたアルゴリズムである」という説明をどう評価すべきですか。
用語集(5語)
- トークンバケット (token bucket)
- 「トークンレート r」と「バケットサイズ b」で表す規定の形式。RFC 2216 は、あらゆる時間区間において送出データ量が `rT+b` を超えてはならない、と定義する(r は長期に持続可能なレート、b は短期の超過許容量)。ただし RFC 2216・2697 とも Informational。
- はみ出した分の扱い
- トークンバケットの定義には含まれない。RFC 2216 は Token Bucket Filter について「不適合パケットへの具体的な処置はこの定義では規定しない」と述べ、ベストエフォートへの格下げ・**破棄**・**印付け**を一般的な動作として挙げるにとどまる。捨てるか遅らせるかは設計者の選択。
- リーキーバケット (leaky bucket)
- nginx公式が `limit_req` の方式として自称する呼び名。ただしnginx自身はこの語を定義していない。RFC 3290(Informational)付録Aはトークンバケットと並べて説明し(主にインタフェースから出るトラフィックの整形に使う、とする)、RFC 2381 §2.2 は GCRA の別名として挙げる。nginxがどの説明に従うかは公式に書かれていない。
- burst / nodelay
- `burst` は遅延させられる超過リクエストの最大数で、**既定は0**。超過分はまず遅延させられ、その数が `burst` を超えると「エラーで終了(terminated with an error)」する。`nodelay` は枠内の待ちを省くだけで、枠そのものは残る。
- 429 / limit_req_status
- 429 Too Many Requests は RFC 6585 §4 の定義で、「一定時間内にリクエストを送りすぎた」ことを示す。同節は429のレスポンスを**キャッシュに保存してはならない**とも規定する。nginxが拒否時に返すコードは `limit_req_status` で決まり、**既定は503**。
出典(一次ソース)
- nginx ngx_http_limit_req_module — leaky bucket / rate / burst / limit_req_status ↗
- RFC 2216 — Token Bucket と rT+b(Informational) ↗
- RFC 2697 §3 — トークンの加算と減算(Informational) ↗
- RFC 3290 Appendix A — Token Buckets and Leaky Buckets(Informational) ↗
- RFC 2381 §2.2 — GCRA, also known as a "leaky bucket" algorithm ↗
- RFC 6585 §4 — 429 Too Many Requests ↗
- RFC 9110 §10.2.3 — Retry-After ↗