代表的な面接トピック

システム設計面接:耐障害性の高いブートストラップAPIの設計

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

質問

クライアントがファーストスクリーンをレンダリングする前に、プロフィール、権限、およびパーソナライズされたフィードを必要としています。部分レスポンス、依存関係の障害、リトライ、可観測性を含め、300ミリ秒のp95バジェット、毎秒2,000リクエストのブートストラップAPIを1つ設計してください。

課題とスコープ

ブートストラップエンドポイントは、3つのダウンストリーム呼び出しをオーケストレーションします。プロフィールと権限は正しいシェルを表示するために必須ですが、フィードは古いか存在しなくても構いません。エンドポイントは、クライアントが安全なセクションを個別にレンダリングできるように型定義されたレスポンスを返す必要があります。300ミリ秒のp95バジェットにはオーケストレーションのオーバーヘッドが含まれ、設計ではどのデータをどのくらいの期間キャッシュしてよいかを明記する必要があります。

面接官が評価している点

  • ユーザーに見える要件から並行性、タイムアウトバジェット、障害セマンティクスを導き出す能力。
  • データ欠損、空の結果、依存関係エラーの区別。
  • 負荷を増大させることなく、リトライ、サーキットブレーカー、バルクヘッド、キャッシュポリシーを組み合わせる能力。
  • 拡張可能なレスポンスコントラクトと有用な運用シグナルの設計。

行うべき明確化のための質問

権限データのステール(陳腐化)が許容されるか、フィードデータに鮮度制限があるか、クライアントがプログレッシブにレンダリングできるか、呼び出し間でテナントや認可コンテキストが共有されるかを質問します。権限のステールが一切許可されない場合、それらはクリティカルパスにとどまります。フィードに5分の鮮度目標がある場合、stale-while-revalidateキャッシュによってレイテンシを保護できます。

30秒での回答

ゲートウェイで一度だけ認証を行い、プロフィール、権限、フィードの呼び出しを並行してファンアウトし、アセンブリ用のデッドラインを確保します。必須セクションはフェイルクローズ(fail closed)とし、オプションのセクションは理由コードと鮮度タイムスタンプを伴う明示的な利用不可ステータスを返します。リトライは一時的かつ冪等な呼び出しに限定し、共有バジェットを消費します。依存関係ごとのタイムアウト、バルクヘッド、サーキットブレーカー、ステールキャッシュ、型定義されたproblem-detailsエンベロープにより、1つの障害がシェルを空白にすることを防ぎます。メトリクスでは、セクションごとの成功率、デッドライン枯渇、ステール配信、依存関係の飽和度を追跡します。

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

1. 時間と並行性のバジェットを設定する

毎秒2,000リクエストにおいて、3つの呼び出しを順次実行すると300ミリ秒のバジェットを浪費します。並行してファンアウトし、各依存関係に全体デッドライン未満のデッドラインを割り当て、残りの時間内でアセンブリとシリアライゼーションを収めます。有界(bounded)な接続プールとリクエストごとのキャンセルシグナルを使用し、タイムアウトした依存関係が処理を消費し続けないようにします。

2. 部分レスポンスのセマンティクスを定義する

各セクションのステータス(readystale、またはunavailable)を含む安定したエンベロープを返します。機械可読な理由とデータのタイムスタンプを含めますが、内部ホスト名は漏洩させないようにします。プロフィールや権限のエラーはフェイルクローズするか、ログイン/アクション状態を返す必要があります。フィードのタイムアウトが発生してもシェルは利用可能なままにできます。RFC 9457に準拠したエラーオブジェクトを使用することで、すべてのセクションが失敗したかのように見せかけることなく、リクエスト全体のエラーを記述できます。

3. リトライや障害から依存関係を保護する

リトライは一時的な障害かつ冪等な読み取りのみに限定し、共有デッドライン内で1回のみ実行します。ジッター(ゆらぎ)を追加し、サーキットが開いているときはリトライを停止します。バルクヘッドにより依存関係ごとの並行呼び出し数を制限し、ステールキャッシュまたは有界なデフォルト値でオプションのフィードデータを処理します。サーキットブレーカーは、ダウンストリームの連続した障害がゲートウェイのキャパシティをすべて消費してしまうのを防ぐのに役立ちますが、タイムアウトやリカバリプローブの代わりになるわけではありません。

4. コントラクトの拡張と可観測性の確保

フィールドは追加的にバージョニングし、クライアントが未知のセクションを無視できるようにし、リクエスト相関識別子(correlation identifier)を含めます。依存関係のレイテンシ、タイムアウトの原因、サーキットの状態、キャッシュの経過時間、セクションのステータス、ペイロードサイズを記録します。ファンアウトツリーをトレースし、低速なリクエストをサンプリングし、必須セクションの失敗率、ステールの経過時間、リトライ量についてアラートを設定します。コントラクトテストでは、権限は準備完了、プロフィールはステール、フィードは利用不可といった混在した結果をカバーする必要があります。

優れた回答例

データの鮮度とプログレッシブレンダリングが許可されるかを確認します。ゲートウェイで一度認証し、3つの読み取りをファンアウトして、300ミリ秒の全体バジェット内で呼び出しごとのデッドラインを割り当てます。セクションレベルでreadystale、またはunavailableのステータスを返します。必須のアイデンティティと権限はフェイルクローズとし、フィードデータは有界なステールキャッシュから取得される場合があります。ジッター付きのリトライは一時的で冪等な読み取りに対してのみ1回許可され、依存関係のバルクヘッドとサーキットブレーカーによって保護されます。メトリクスとトレースによってセクションの失敗とデッドラインの圧迫状況を可視化し、追加的なバージョニングによって古いクライアントの動作を維持します。

よくある間違い

  • 依存関係を順次呼び出す → レイテンシが加算されてバジェットを超過する → 有界なプールとデッドラインを用いてファンアウトする。
  • 曖昧なnullを含むHTTP 200を返す → クライアントが空のデータと障害を区別できない → 明示的なセクションステータスと理由コードを使用する。
  • すべてのレイヤーですべてのエラーをリトライする → 1つの障害がリトライストームに発展する → エラーを分類し、1つの共有リトライバジェットを強制する。
  • ポリシーなしで権限をキャッシュする → アクセス権がその認可よりも長く残存してしまう → 鮮度の上限を定義するかフェイルクローズにする。
  • 1つのグローバルなサーキットを使用する → フィードの障害がアイデンティティデータをブロックする → ブレーカーとバルクヘッドを依存関係ごとに分離する。
  • 合計レイテンシのみをログに記録する → オプションと必須の障害が区別できない → セクションレベルのメトリクスとトレースを出力する。

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

フィードは低速ですが、ユーザーはシェルをすぐに必要としています。何を変更しますか?

フィードのデッドラインを短縮し、データの経過時間が許容範囲内であれば有界なステール結果を提供し、それ以外の場合はunavailableを返します。クライアントが後からフィードを読み込めるよう、シェルのレスポンスは独立した状態を維持します。

あるリージョンで権限データがステールしています。それを配信してもよいですか?

認可ポリシーがそのステール状態を明示的に許可しており、レスポンスがその経過時間を伝えている場合にのみ可能です。機密性の高いアクションについては、信頼できるソース(authoritative source)に対して再確認を行い、不確実な場合はフェイルクローズにします。

リトライが300ミリ秒を超えないようにするにはどうすればよいですか?

ファンアウトコンテキストを通じて1つの絶対デッドラインを渡します。各リトライの前に、試行とアセンブリのための時間を確保します。残っていない場合は、完了できない処理を開始する代わりに、そのセクションのタイムアウト状態を返します。

サーキットが開いた後、依存関係が回復しました。トラフィックはどのように復旧しますか?

クールダウン期間の後、少数のハーフオープン(half-open)プローブを送信します。成功したプローブが同じタイムアウトおよびエラー基準を満たした場合にのみサーキットを閉じます。それ以外の場合は、次にプローブを送信する時間を観測可能な状態にしてサーキットを開いたままにします。

公開情報ソース

関連する質問

関連面接ツール

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

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

ツールを見る