代表的な面接トピック

システムデザイン面接:マルチテナント対応の API レートリミッターをどのように設計しますか?

システム設計難しい
Offer.cc 編集チーム公開日 更新日

質問

マルチテナント API プラットフォーム向けのレートリミッターを設計してください。無料テナントには 1 分あたり 100 リクエスト、有料テナントには 10,000 リクエストを許可し、プラットフォームは複数のアプリケーションインスタンスおよびリージョンにまたがって動作します。アルゴリズムを比較し、共有状態、429 レスポンス、障害時の縮退、検証について説明してください。

設問と適用されるコンテキスト

マルチテナント API プラットフォーム向けのレートリミッターを設計してください。無料テナントには 1 分あたり 100 リクエスト、有料テナントには 10,000 リクエストを許可し、プラットフォームは複数のアプリケーションインスタンスおよびリージョンにまたがって動作します。アルゴリズムを比較し、共有状態、429 レスポンス、障害時の縮退、検証について説明してください。

これはバックエンド、プラットフォーム、システムデザインの面接に適した設問です。公開されている面接記録には、時間ベースのリフィルを行うユーザーごとのトークンバケットのコーディング問題が見られます。システムデザインの教材では、レート制限をマルチテナント API の標準的な議論として扱っています。中核となるスキルは、特定のクラウドプロバイダーの設定を暗記することではなく、共有状態、テナントポリシー、トラフィック保護です。

面接官が評価するポイント

  • 平均レート、バースト容量、テナント間の公平性、全体的な保護を区別して考えているか。
  • トークンバケット、固定ウィンドウ、スライディングウィンドウの境界とコストを説明できるか。
  • ホットキー、パーティション、クロックに対応しつつ、インスタンス間で共有状態がアトミックに更新されているか。
  • 429 レスポンス、リトライのヒント、縮退、テレメトリによって、リミッターが単一障害点(SPOF)になるのを防いでいるか。

回答前の明確化のための質問

  • 対象は API キー、テナント、ユーザー、IP、またはそれらの組み合わせですか?匿名リクエストはどのようにグループ化されますか?
  • ポリシーは平均レート、厳格なスライディングウィンドウ、または制御されたバーストですか?日次クォータもありますか?
  • マルチリージョンでは厳密なグローバル適用が必要ですか?それとも、可用性を確保するために短時間の超過は許容されますか?
  • 拒否されたリクエストは即座に 429 を受け取るべきですか、それとも有界キューに入るべきですか?依存関係はそもそもバーストを許容できますか?

30秒の回答フレームワーク

ゲートウェイでおおまかな IP および未認証の保護を行い、アプリケーションレイヤーでテナントを認識したポリシーを適用します。API が短時間のバーストを許容する場合は、トークンバケットから始めます。各テナントのバケットは、トークンと最終リフィル時刻をアトミックな共有状態に保存します。制限を超えたリクエストは、計算可能なリトライのヒントとともに 429 を受け取ります。厳密なグローバルカウントが不要な場合、リージョンごとのクォータとグローバルな超過監視により、レイテンシと引き換えにわずかな精度を譲歩します。ストレージ障害時は、アラートを伴う明示的な fail-open または fail-closed ポリシーを使用します。

ステップバイステップの詳細解説

予算と公平性の定義

1 分あたり 100 リクエストは無料テナントの長期的な予算であり、有料テナントの場合は 10,000 リクエストです。どちらも独立したバースト容量を必要とします。単一のグローバルカウンターではどちらのポリシーも表現できません。大規模なテナントがデータベース接続を枯渇させないように、グローバルガードで集計 RPS に上限を設ける必要があります。無関係なエンドポイントが誤ってクォータを共有しないように、ポリシーキーにはテナント ID、API アクション、ポリシーバージョンを含める必要があります。

アルゴリズムの選択

アルゴリズム主なセマンティクスコストとリスク適した用途
トークンバケット制御されたバーストを伴う有界平均レート2 つの状態値。リフィルと消費はアトミックである必要がある短時間のバーストを許容するユーザー向け API
固定ウィンドウ一定期間のリクエストをカウント境界部分で設定レートの最大 2 倍に達する可能性がある近似値が許容されるシンプルなルール
スライディングウィンドウログアクティブなウィンドウ内の正確なカウントタイムスタンプを保存。メモリとクリーンアップのコストが高い厳密な精度を必要とする小規模な母集団
スライディングウィンドウカウンター隣接するウィンドウからの加重推定近似値だがメモリ効率が良い。誤差を明記する必要がある大規模な公平性制限

AWS API Gateway はトークンバケットによるスロットリングを文書化しています。トークンレートは定常状態のトラフィックを表し、バーストはバケット容量を表します。429 を返すことはできますが、制限値は絶対的な数学的上限ではなくベストエフォートの目標です。ポリシーの意図とプラットフォームの保証は明確に区別してください。

トークンバケットの不変条件

トークン数は常に [0, capacity] 内にとどまります。到着時、経過時間にリフィルレートを掛けた分だけリフィルし、容量を上限として制限した上で、トークンが少なくとも 1 つあるか確認します。許可されたリクエストは 1 つ消費します。この順序により、アイドル期間があってもバースト制限までしか蓄積されず、ダウンタイム後に無制限のクレジットが作成されるのを防ぎます。

~~~text allow(key, now): state = atomicRead(key) elapsed = max(0, now - state.lastRefill) refilled = min(capacity, state.tokens + elapsed * rate) if refilled < 1: atomicWrite(key, refilled, now) return reject(429) atomicWrite(key, refilled - 1, now) return allow ~~~

本番環境では、この疑似コードは 1 つの Lua スクリプト、トランザクション、または同等の compare-and-swap 操作として実行する必要があります。独立した 2 つの読み取り呼び出しと書き込み呼び出しを行うと、並行処理下でトークンを過剰に払い出してしまう可能性があります。タイムスタンプは信頼できるモノトニック(単調増加)ソースから取得する必要があり、クライアントから送信させてはなりません。

配置と共有状態

ゲートウェイは明らかな IP フラッドや未認証のトラフィックをブロックし、アプリケーションレイヤーはテナント、ユーザー、またはエンドポイントのポリシーを適用します。すべてのインスタンスがローカルメモリでのみカウントしている場合、ロードバランシングによって 1 つのテナントが複数のインスタンスに呼び出しを分散させ、複数の枠を取得できてしまいます。共有 Redis、アトミックなキーバリューストア、または条件付き書き込みを行うデータベースで状態を保持できます。選択はレイテンシ、精度、障害モデルに依存します。

マルチリージョンの選択肢は明確です。1 つのグローバルストアはクロスリージョンレイテンシを犠牲にしてより正確な割り当てを提供し、独立したリージョンバケットは高速ですが短時間超過する可能性があり、リージョン割り当てにグローバルガードを組み合わせたものはその中間に位置します。グローバルな厳格な制限を主張する前に、可用性よりも精度が重要かどうかを確認してください。

拒否、縮退、およびテレメトリ

Retry-After または残量クォータヘッダーとともに 429 を返します。クライアントは、即座にリトライしてフィードバックループに陥るのではなく、有界バックオフを使用する必要があります。リミッターストレージに障害が発生した場合、リスクの高い書き込みは通常 fail-closed にするか有界キューに入れ、リスクの低い読み取りは短時間 fail-open にする場合がありますが、ローカルサーキットブレーカー、有効期限、集計上限が必要です。許可率と拒否率、テナントごとのクォータ到達、ストレージレイテンシ、ホットキー、スクリプトエラー、実際の下流負荷を個別に監視してください。

質の高い模範解答

2 つのレイヤーを使用します。ゲートウェイが IP や未認証のフラッドから保護し、アプリケーションがテナントおよびエンドポイントのポリシーを適用します。無料テナントと有料テナントには、個別のレート値とバースト値があります。API は通常、長期的な平均を適用しながら短時間のバーストを許容できるため、デフォルトとしてトークンバケットを使用します。各バケットはトークンと最終リフィル時刻を保存し、1 つのアトミックなスクリプトが共有ストレージ内でリフィル、チェック、消費を実行します。

リージョンごとにすべてのリクエストに対して厳密なグローバル結果が必要とされない場合は、リージョンごとのクォータを割り当て、グローバルテレメトリを使用して異常な超過を検出します。決済やクォータの精算を厳格に行う必要がある場合は、より強い整合性を持つ決定ポイントを使用し、レイテンシを受け入れます。制限を超えたリクエストは 429 とリトライガイダンスを受け取ります。ストレージ障害時は、エンドポイントのリスクに応じて fail-closed、短時間の fail-open、または有界キューを選択します。その後、単に 429 の数だけでなく、バースト容量、ウィンドウ境界、テナントの公平性、障害復旧、下流の負荷を負荷テストします。

よくある間違い

  • 「Redis カウンターを使用する」→ ポリシーセマンティクスやアトミック性がない → バケットの状態、スクリプトの境界、障害時の動作を指定してください。
  • すべてのテナントに 1 つのクォータを適用する → 大規模テナントが小規模テナントを圧迫する → テナントとエンドポイントごとにポリシーを分割し、グローバルガードを追加してください。
  • 固定ウィンドウを厳格な 1 分あたりの上限として扱う → 境界トラフィックがレートの最大 2 倍に達する可能性がある → 誤差を説明し、必要に応じてスライディングまたはトークンバケットを選択してください。
  • リミッターストレージ障害時に無制限に開放する → 依存先が真っ先にダウンする → ビジネスリスクに応じて有界な縮退を選択し、アラートを設定してください。
  • クライアントに 429 の即時リトライを指示する → 拒否されたトラフィックがさらなる負荷になる → リトライガイダンス、ジッター、上限を提供してください。

フォローアップの質問と回答

1 つのテナントが 20 個のインスタンスにわたって割り当てを増殖させるのを防ぐにはどうすればよいですか?

すべてのインスタンスから参照可能な共有ストレージにバケットの状態を配置し、テナントポリシーキーの下でアトミックに更新します。ローカルカウンターしか利用できない場合は、その結果を近似値とし、ゲートウェイ全体の上限を追加してください。グローバルな精度があると主張してはいけません。

1 分あたり 100 リクエストをリージョン間で厳格に適用する必要がある場合はどうしますか?

より強い整合性を持つグローバルな決定ポイントを使用するか、テナントのホームリージョン経由で消費をシリアライズします。その代償として、クロスリージョンレイテンシが発生し、リージョン障害時の可用性が低下します。短時間の超過が許容される場合は、リージョンクォータとリコンシリエーションを併用し、許容誤差を SLO に明記してください。

低速なリミッターが API 全体を道連れにするのを防ぐにはどうすればよいですか?

リミッター呼び出しの周囲に厳格なタイムアウトとサーキットブレーカーを設定します。ストレージ停止に備えてローカルの安全上限を維持し、エンドポイントのリスクに応じて縮退させます。リミッターのレイテンシ、タイムアウト、スクリプト障害をビジネス成功率とは独立して追跡してください。

429 を返す代わりにキューイングすべきなのはどのような場合ですか?

作業が非同期であり、待機時間がユーザーの許容範囲内に収まり、依存関係に平滑化が必要な場合にのみキューイングします。キューには上限を設け、満杯の場合は拒否する必要があります。対話型の読み取りや上限のない待機は、通常、正直に 429 を返すべきです。

固定ウィンドウの境界欠陥をテストするにはどうすればよいですか?

ウィンドウが終了する直前に 1 回バーストを送信し、開始直後にもう 1 回送信して、すべてのローリング間隔でリクエストをカウントします。トークンバケットのアイドルリフィル、フルバケットバースト、制御されたクロックを使用した並行消費でも同様のテストを繰り返します。

公開情報ソース

関連する質問

関連面接ツール

システム設計の回答には「回答する」を使用

まず要件を明確にし、スケール、アーキテクチャ、コンポーネント選定、トレードオフの順に進めます。

ツールを見る