ブログ一覧

レート制限の設計:固定窓・移動窓・トークンバケットと複数台の上限

· 約9分で読めます

毎分100件なのに短時間に200件通るのはなぜか。方式の比較、バーストの計算、原子的な判定、全台の送信枠、1件の処理量まで、帳票APIを例に4件のTipsで説明します。

レート制限の方式は、守りたい対象から決める

架空の帳票APIは、利用者別の公平性と、外部サービス全体の許容量を守る必要があります。利用者ごとの枠だけでは共有APIキーの枠を守れず、外部の枠だけでは1社による占有を防げません。利用者IDは認証済みの値から確定し、キーの自己申告で別の枠へ逃げられないようにします。

要件候補注意点
時刻で区切る各1分に100件固定窓窓をまたぐ短い集中を許す
直近60秒に最大100件正確な移動窓ログ要求時刻の保持と原子的な判定が必要
平均流量と短い集中を別に決めるトークンバケット任意区間の許容量は容量+補充量で考える
大量トラフィックで概算を許す重み付きの窓カウンター正確な移動窓と同じ保証にしない

固定窓と移動窓:境目で何が通る?

1分100件の固定窓で、12:00:59に100件、12:01:00に100件を送ると、それぞれの窓は100件でも短時間に200件が通ります。直近60秒の許可済み要求を正確に保持する方式なら、後の100件を数える時点で前の100件が残っています。区間を(t−60秒, t]と定義し、同時刻の要求も別の一意IDで記録します。

判定、期限外の記録の除去、許可した要求の追加を、他の要求が途中へ割り込めない1つの処理にします。これを原子的な処理と呼びます。記録の容量とキーの保持期間も必要です。重み付きカウンターは状態を減らせますが、窓内の偏りを近似するため、厳密な直近件数とは区別します。Cloudflareの公開記事は、この近似の考え方を説明する実装例です。

トークンバケットは容量Bと補充速度rを分ける

1要求で1トークンを消費し、容量20、毎分100トークンを連続的に補充する仮定なら、満杯から長さT秒の区間で許可できる数は最大floor(20+100T/60)です。floor(フロア)は小数点以下を切り捨てて整数にする関数です。2秒なら最大23件、60秒なら最大120件。平均毎分100だから、どの60秒も100件以下になるわけではありません。補充を離散的にする実装では境界の仕様も変わります。

共有する一つのカウンターで、補充・許可判定・消費を不可分に行う疑似コード。分散障害への保証を含む完成実装ではない
elapsed = max(0, limiter_now - last_refill)
tokens = min(capacity, tokens + elapsed * refill_rate)
last_refill = max(last_refill, limiter_now)
allowed = tokens >= 1
if allowed:
    tokens -= 1
persist(tokens, last_refill)
return allowed

同じ信頼できる時間源と単位を使い、時計の逆行や状態の初期化で意図せず枠を増やさないようにします。RedisのLuaはサーバー内の実行を原子的にできますが、クライアントでGETしてから別にSETする処理には競合が残ります。スクリプトの原子性は、フェイルオーバー時の状態喪失まで防ぐ保証ではありません。

複数台・複数地域で、誰が全体の枠を持つ?

1台20件/秒なら10台で最大200件/秒、40台で800件/秒です。共有先の上限300件/秒を守るなら、通常処理・再試行・バッチ・新旧の並走を合わせます。独自の設計例では余裕を見て全体240件/秒、その内訳を通常200・再試行40とします。これは推奨値ではなく、外部の時間窓とバースト規則にも合わせる必要があります。 下流を守る枠は各送信の直前で適用し、バッチ内の呼び出しと再試行も1試行ずつ数えます。入口で1要求を数えるだけでは、後で生まれる複数の送信を制御できません。送信開始数の上限と、通信遅延で集中し得る下流への到着数は別なので、受信側の厳密な時間窓まで保証したとは扱いません。

共有状態へ毎回問い合わせる方式は調整しやすい反面、制限基盤への依存が増えます。地域に枠を分配するなら、重ならない配分と再配分の手順が必要です。到達不能時に許可するか拒否するかも、業務と下流保護の要件から決めます。厳しい上限があるのに無制限で通す経路を残すと、通常時だけ正しい設計になります。

全利用者を1つのカウンターへ集中させると、そのキー自体がホットキーになり得ます。単純にN分割して各キーに全量を許すと上限がN倍になります。分割するなら予算も配分し、厳密性・利用効率・遅延のどれを優先するかを明示します。Redis Clusterでもキー単位の配置や非同期複製の制約は残ります。

回数・並列数・1件の処理量は別の制限

100項目が各2回の外部API呼び出しをするなら、総呼び出し数は200回です。同時に5項目を処理し、各項目内では2回を順番に実行する前提なら、同時の外部呼び出しは高々5回です。項目内で並列化すればこの値も変わります。総件数と毎秒の開始数と実行中の数を混ぜません。

1要求から生まれる負荷も抑える

受信中の本文と展開後サイズを制限 → 項目数・ページサイズ・検索範囲を検証 → 利用者別と全体の枠を判定 → 実行中の数と待機期限を制限 → 超過は契約したエラーまたは非同期ジョブへ

nginxの本文サイズ制限は入口のバイト数を抑えますが、短い入力から生まれる大量のDB処理は別に制御します。429を返す場合は、再送の時期や回数をクライアント契約に含めます。Retry-AfterはRFC 6585で指定可能ですが、それだけで安全な書き込み再試行や成功を保証するものではありません。

境界・同時実行・制限基盤の障害を試す

  1. 窓の直前直後と同じ時刻に要求を集中させ、仕様どおりの許可数を確認する。
  2. 10台から40台へ増やし、再試行とバッチを混ぜて、送信先で合計を計測する。
  3. 制限状態のタイムアウトやフェイルオーバーを起こし、許可不明時と状態喪失時の挙動を確かめる。
  4. 大きい本文・多数項目・遅い下流を試し、メモリ・並列数・待ち行列が上限に収まるか観測する。

参考にした一次資料

以下の公式資料は2026年10月27日に確認しました。数値例とシナリオは独自の仮定です。設定例は説明用で、実サービスでの実行や性能測定は行っていません。

この記事で深掘りしたTips