Dev Study
← Webを支えるソフトウェア
レッスン 10 / 13·約 9 分·第3期 Webを支えるソフトウェア

送りすぎを止める — レート制限の原理

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口今ここ
  6. アプリ処理
  7. 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 が短期の超過許容量です。

同じ2パラメータが、名前を変えて現れる表
持続レート 短期のバースト許容
RFC 2216 r b
nginx 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` は何をしますか。

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

TCPの輻輳ウィンドウ(第24週)

送信側は網がどれだけ詰まっているかを知りません。パケット損失や重複ACKという間接的な兆候から推し量り、cwnd を自分で増減させます。cwnd は送信側のローカル変数で、パケットには現れません。

上限の在り処と伝わり方表
上限の在り処 送信側のローカル変数(cwnd)
誰が決める 送信側が推定する
超過の合図 パケット損失・重複ACK3回
超過時の挙動 cwnd を減らす(自律調整)
具体例 B

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; が要る。

自分の現場で確かめる

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

理解度チェック

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

問題 1 / 4正解: 0

「リーキーバケットは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**。