レート制限:トークンバケットと漏れバケットの実際の違い
トークンバケットは突発を許し、漏れバケットは一定速度に均します。選び間違えると、正常な利用者が制限されたり、制限が機能しなかったりします。
二つの古典的な模型は同義語のように扱われますが、出力の形がまったく違います。一方は突発を保ち、他方は消します。
トークンバケット:突発を許す
容量 C のバケットに一定速度でトークンが溜まります。要求はトークンを一つ取り、無ければ制限されます。
- 速度 R が長期的な平均スループット
- 容量 C が許容する突発の大きさ
アイドル後はバケットが満杯なので、C 件が瞬時に通ります。ページが一度に 20 件要求するような正常な挙動にはこれが必要です。 漏れバケットはそれを平準化し、利用者には一件ずつ読まれるのが見えます。
漏れバケット:一定の出力
要求は待ち行列に入り、一定速度で出ます。満杯なら破棄されます。
出力は一定で、代償は遅延の追加です。要求は待ち行列で待ちます。下流の保護は強いが、体験は悪化します。
比較
| 観点 | トークンバケット | 漏れバケット |
|---|---|---|
| 出力速度 | 可変、最大 C | 一定 |
| 突発 | 許す | 許さない |
| 遅延 | 加えない | 加える |
| 守る対象 | 余力のあるサービス | 脆い下流 |
どちらを選ぶか
問いは一つです。下流は突発を消化できるか。
| 場面 | 選択 | 理由 |
|---|---|---|
| 公開 API ゲートウェイ | トークンバケット | 通常利用が突発的 |
| 有料の外部 API | 漏れバケット | 秒単位の課金で突発に意味がない |
| データベース接続プール | 漏れバケット | 突発で接続が尽きる |
| 利用者ごとの上限 | トークンバケット | 短時間の集中を罰する必要はない |
分散環境の落とし穴
プロセス内の計数は、複数インスタンスでは上限を台数分に増やします。共有状態(Redis の INCR と期限)を使うか、各インスタンスに 上限 / 台数 を割り当てます。
Redis では原子性に注意します。GET してから SET は競合します。INCR と EXPIRE、あるいは両方を行う Lua スクリプトを使います。
どの状態コードを返すか
制限時は 429 Too Many Requests を返し、Retry-After で待ち時間を伝えます。Retry-After の無い 429 は盲目的な再試行を招き、自分の流量を増やします。
トークンバケットは長期平均と突発予算を、漏れバケットは一定出力を扱います。下流がどちらを消化できるかを先に決めてください。

コメント
…