代表的な面接トピック

システムデザイン面接:マルチテナントクォータ予約サービスの設計

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

質問

複数のプロダクトが各テナントのストレージまたはリクエストキャパシティを共有しています。reserve(予約)、commit(確定)、release(解放)、および期限切れ回収を備えたクォータサービスを設計し、並行性、障害、マルチリージョンでの動作、および課金調整について説明してください。

プロンプトとスコープ

クォータサービスは、今リソース量を確保できるかどうかを判定するものであり、ファイルを移動したりビジネスオペレーションを実行したりするものではありません。設計では、共有インフラストラクチャの公平性を保ちながら、信頼できる正規のキャパシティ、一時的な予約、および最終的な使用量を明確に分離する必要があります。

面接官がテストしていること

  • レート制限、キャパシティクォータ、予約、およびコミットの違いを区別できるか。
  • 並行更新と冪等な API により過剰割り当て(オーバセル)を防止できるか。
  • 呼び出し元のクラッシュやリトライ後に、期限切れの予約を回収できるか。
  • テナントの公平性、ホットキー、リージョン間の一貫性、およびデグラデーションについて考察できるか。

尋ねるべき明確化のための質問

リソースの次元(リクエスト数、バイト数、または並行ジョブ数)、スコープ(テナント、プロジェクト、ユーザー、またはエンドポイント)、バーストおよび階層ルール、予約の有効期間、課金の一貫性、そしてリージョン間リクエストで単一の権限元(authority)を使用できるかどうかを確認します。

30秒の回答

リソースキーごとに limitcommittedreserved を持つ信頼できるステートマシンを保持し、reserve、commit、release、および query API を公開します。Reserve はアトミックな条件付きチェックを実行し、冪等な reservation_id と有効期限を保持します。Commit は予約を使用量へと変換し、release または期限切れによってキャパシティが返却されます。不変なイベント台帳と調整(reconciliation)によってドリフトを修復し、ルーティングと公平性ルールによって単一のホットなテナントが他のテナントのリソースを枯渇させるのを防ぎます。

ステップごとの詳細解説

1. 状態と API の境界を定義する

各クォータレコードは、リソースキー、制限値(limit)、コミット済み量、予約済み量、バージョン、および更新時刻を持ちます。reserve は reservation_id、利用可能量、および有効期限を返します。commit は自身の予約分のみを消費できます。release は再試行可能です。query は残りのキャパシティと鮮度を返します。呼び出し元は、リソース書き込み前に予約し、成功後にコミットし、失敗またはキャンセル時に解放します。

2. 並行処理下での過剰割り当てを防ぐ

リソースキーの更新は、1つのアトミックなトランザクションまたは線形化可能なストレージ操作である必要があります。すなわち、committed + reserved + amount <= limit の場合にのみ予約します。同じ冪等性キーの再試行には元の結果を返し、パラメータの変更は拒否します。ホットキーはテナントやリソースごとにシャーディングできますが、一時的な超過を許可するかどうか、およびシャードをどのように調整するかを設計で明示する必要があります。

3. リークした予約を回収する

呼び出し元は予約後にクラッシュする可能性があるため、有効期限を保存し、スキャナーや遅延キューを介して回収します。回収とコミットの競合は状態条件によって制御します。コミットされた予約は解放できず、解放された予約はコミットできません。遅延したクリーンアップが利用可能なキャパシティと誤認されないよう、回収の遅延(reclaim lag)を追跡します。

4. 共有プールと公平性を処理する

共有プールには、グローバル、テナント、およびプロジェクトの制約が存在する場合があります。これらを固定の順序かつ単一のトランザクション内でチェックします。単一のテナントがプールを独占しないよう、テナントクォータ、優先度、または重み付け公平性に基づいてバーストを割り当てます。ブラインドリトライを減らすために、残りのキャパシティ、リトライタイミング、またはキューの状態を返します。

5. 障害、リージョン、および調整を設計する

権限元が利用できない場合、未知の状態から課金対象のキャパシティを消費するのを避け、リスクの高い予約は保守的に拒否します。リスクの低い読み取りはタイムスタンプ付きの近似値を返しても構いません。テナントをホームリージョンに固定するか、明示的なローカル超過境界を定義して非同期に調整します。すべての reserve、commit、release を不変の台帳に記録し、実際のリソース使用量と比較して、冪等な補償ジョブでドリフトを修復します。

優れた回答例

リソース、テナント階層、予約の有効期間、および一貫性の要件を確認します。サービスはリソースキーごとに limit、committed、reserved、および version を保持し、reservation_id に基づく reserve、commit、release、および query 操作を公開します。Reserve は合計値をアトミックにチェックし、リトライ時には同じ結果を返します。呼び出し元は書き込み成功後にコミットし、失敗時に解放し、回収処理が期限切れを処理します。共有プールはグローバル制限とテナント制限をまとめてチェックし、固定ルーティング、優先度、または重み付け公平性を使用します。権限元の障害時には、高リスクな書き込みはフェイルクローズ(fail closed)します。リージョン間の配置には、テナントのホームリージョンまたは明示的な制限付き超過を使用します。不変の台帳と定期的な調整により、追跡されているクォータと実際の使用量との整合性を維持します。

よくある間違い

  • クォータを 429 のみを返すレートリミッターとして扱ってしまう。
  • アトミックな条件なしに、残りのキャパシティを読み取って後から書き込んでしまう。
  • 予約のアイデンティティ、有効期限、および冪等性のセマンティクスを省略してしまう。
  • クラッシュした呼び出し元がキャパシティを永久に保持することを許してしまう。
  • 共有プール、階層構造、およびノイジーネイバーに対する公平性を無視してしまう。
  • ストレージ障害時にフェイルオープン(fail open)し、後から手動でカウンターを修正しようとする。

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

呼び出し元がコミット前にタイムアウトしました。リトライすべきですか、それとも再予約すべきですか?

まずは同じ reservation_id を使用してクエリするか、コミットをリトライします。古い予約が解放されたか期限切れになったことを確認した後にのみ、新しい予約を作成します。

複数のプロダクトが1つのテナントクォータを共有している場合はどうしますか?

テナントを共有の親、プロダクトを子制約として扱い、単一のトランザクション内で両方をチェックします。クライアントがキューイングやデグラデーションを行えるよう、どのレイヤーが枯渇したかを返します。

複数のリージョンが並行して予約できますか?

オーバセルが許されない課金キャパシティの場合は、単一の権限元または強力な協調メカニズムを使用します。制限付きの超過が許容される場合は、各リージョンに借入制限を割り当て、負債を明示的に調整します。

クォータのドリフトはどのように検出しますか?

台帳のイベントおよびリソーススキャンを、テナントおよびリソースタイプごとのコミット済み、予約済み、および実際の使用量と比較します。差異に対してアラートを発報し、監査可能で冪等な補償ジョブを実行します。

公開情報ソース

関連する質問

関連面接ツール

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

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

ツールを見る