プロンプトとコンテキスト
あるサービスがデータベース、決済プロバイダー、レコメンデーションシステムを呼び出しています。レコメンデーションの処理時間が10ミリ秒から1秒へと低下しました。インフライトのリクエストが増加してスレッドやコネクションが消費され、レコメンデーションを必要としないAPIまでもが失敗し始めます。遅延している依存関係の影響が関連機能のみにとどまるよう、バルクヘッドを設計してください。
AWS Builders’ Libraryでは、これをレイテンシ起因の同時実行過負荷(latency-driven concurrency overload)と説明しています。タイムアウトは同時実行数の制限ではありません。リソースは各依存関係またはクライアントの周囲で分離され、サービスとダウンストリームシステムの両方を保護する必要があります。
面接官が評価するポイント
- サービス時間が長くなると、到着率が同じであってもインフライトの処理が増加するというリトルの法則(Little’s Law)の直感を説明できるか。
- 単一の遅延した依存関係がグローバルな容量を消費し尽くさないよう、依存関係、API、テナント、またはリソースプールごとに同時実行数を割り当てられるか。
- ハードクォータ、ソフトクォータ、動的クォータを比較し、公平性と使用率のトレードオフを理解しているか。
- 高速な拒否、縮退運転、デッドライン、サーキット動作、復旧、可観測性を設計できるか。
- 無制限のキューや枯渇(スターベーション)を発生させることなく、無関係なAPIの健全性が維持されることを証明できるか。
最初に明確にすべき質問
- どのAPIがレコメンデーションを呼び出し、どのAPIがクリティカルであるか?縮退可能なパスはあるか?
- スレッド、コネクション、メモリ、キューの制限値と、現在の同時実行数の配分はどうなっているか?
- 依存関係はキャンセル、冪等性、バッチ処理、またはキャッシングをサポートしているか?呼び出し元のデッドラインはどのように伝播されるか?
- 予算(クォータ)はAPI、依存関係、テナント、またはAvailability Zoneごとに分離されているか?容量の借用(borrowing)は必要か?
- 障害時にどのようなユーザー体験、エラー規約、復旧目標が適用されるか?
30秒での回答
「依存関係ごとにバウンドされた同時実行バルクヘッドとキューを作成し、クリティカルなAPIと縮退可能なAPIでプールを分離します。リクエストにはデッドラインを付与し、予算を超過した処理は無限に待機させるのではなく、高速に失敗させるかキャッシュを返します。リソース使用率のためにソフトクォータを使用しつつ、制限付き借用を伴うハードなグローバルおよびクラス別制限を設けます。メトリクスでは、依存関係およびAPIごとにインフライト処理、拒否、待機時間、タイムアウト、縮退、復旧をカバーします。遅延依存関係のテストを実施しても、無関係なAPIが健全なままである必要があります。」
ステップごとの解決策
ステップ 1: レイテンシ起因の同時実行数を定量化する
依存関係ごとに到着率、サービス時間、インフライトリクエスト、タイムアウトを測定します。サービス時間が10ミリ秒から1秒に増加すると、到着率が同じでもインフライト処理は約100倍に膨らむ可能性があります。バルクヘッドのサイズを決定する前に、ボトルネックとなるリソースを特定してください。
ステップ 2: リソースプールをパーティショニングする
各依存関係に専用のコネクションプール、セマフォ、バウンドされたキューを割り当てます。同じ依存関係であってもクリティカルなAPIと縮退可能なAPIを分割します。スレッド、コネクション、状態が別々のカウンタの裏で共有されたままになっていないか、分離が実質的であることを確認します。
ステップ 3: ハード、ソフト、動的クォータを選択する
ハードクォータはAPIを保護しますが、負荷の偏り(スキュー)がある場合に容量を無駄にします。ソフトクォータはグローバル上限の下でアイドル容量を借用します。動的クォータは負荷に適応しますが、最小保証、最大境界、安全な変動レートが必要です。公平性が重要な場合はテナントごとのパーティションを追加します。
global_limit = 500
payments = hard 150
recommendations = soft 200, borrow <= 100
other_apis = reserved 50ステップ 4: 意図的に拒否および縮退させる
利用可能なパーミットがない場合は、無制限のキューに入れるのではなく、明示的なエラーを返すかキャッシュ結果を返します。呼び出し元のデッドラインを尊重し、キャンセル可能な処理はキャンセルします。リトライにはバジェット、バックオフ、冪等性が必要です。クリティカルな書き込みをサイレントに縮退させてはなりません。
ステップ 5: 振動を起こさずに復旧する
復旧後は、急激なバーストを避けるためにパーミットを徐々に増やし、ハーフオープンのプローブを使用します。依存関係、API、テナント、セルごとにセグメント化された、拒否、タイムアウト、キュー水位の時系列データを保持します。設定のバージョン管理を行い、ロールバック手段を確保します。
ステップ 6: 分離境界を検証する
単一の依存関係にレイテンシ、エラー、コネクション枯渇を注入します。レコメンデーションの拒否、決済の成功、グローバルなスレッド/コネクション使用量、テールレイテンシを観察します。バースト、テナントの偏り、設定変更、セルの障害をテストします。無関係なAPIのSLOとキューの境界が維持されることが受け入れ基準となります。
強力な回答例
「到着率、サービス時間、インフライト処理を用いて、低速なレコメンデーション依存関係がなぜ同時実行数を急増させるかを説明します。各依存関係には専用のセマフォ、プール、バウンドされたキューを持たせ、クリティカルなAPIと縮退可能なAPIを分離します。決済にはハードな予約を割り当て、レコメンデーションには境界付き借用を許可したソフトクォータを使用します。すべての呼び出しでデッドラインを伝播させ、パーミットが枯渇した場合は高速に失敗させるかキャッシュを返します。」
「復旧にはハーフオープンプローブと段階的なパーミット増加を使用します。メトリクスには、インフライト処理、拒否、待機時間、タイムアウト、縮退ヒット、ダウンストリームエラー、復旧時間を含めます。ドリルではレコメンデーションのみを遅延させ、決済や無関係なAPIのテールレイテンシ、プール、スレッドが制限内に収まることを確認します。」
よくある間違い
- タイムアウトのみを増やす → インフライト処理が増大する → 依存関係レベルの同時実行制限を設定する。
- 1つのスレッドプールやコネクションプールを共有する → 1つの低速な依存関係がサービス全体をダウンさせる → 依存関係とクリティカル度ごとにパーティションを分ける。
- 無制限のキューを使用する → メモリとレイテンシが暴走する → 境界を設けて高速に拒否する。
- あらゆる場所に静的ハードクォータを適用する → 負荷の偏りにより容量がアイドル状態になる → 境界付きのソフト借用を許可する。
- バジェットなしでリトライする → ダウンストリームの過負荷が増幅される → デッドラインを伝播し、試行回数を制限し、冪等性を必須とする。
- 障害が発生した依存関係のみをテストする → 分離が証明されない → 無関係なAPIのSLOも同時に検証する。
フォローアップの質問と回答
なぜタイムアウトは同時実行数の制限にならないのですか?
1回のリクエストの待機時間を制限するだけであり、待機中のリクエストは依然としてスレッド、コネクション、メモリを占有し続けます。依存関係のレイテンシが高くなるとより多くのリクエストがインフライトになるため、パーミット自体を制限する必要があります。
ソフト借用によって1つのAPIがすべてを占有するのをどう防ぎますか?
グローバル上限、APIごとの最小予約枠、最大借用可能量、およびリクレイム(回収)レートを設定します。借用側がその上限に達すると、無限に増大するのではなく高速に失敗します。
キャッシュを返すのが安全なのはどのような場合ですか?
ある程度の古さ(bounded staleness)が許容される読み取り操作の場合、タイムスタンプ付きのキャッシュ値を返します。決済、認証、書き込みの結果を古いデータでサイレントに置き換えることはできません。
分離が機能していることをどのように証明しますか?
単一の依存関係に遅延やエラーを注入し、無関係なAPIのインフライト処理、テールレイテンシ、スレッド/コネクションの使用状況、成功率を検査します。同期して縮退が発生した場合は、共有リソースやキューの境界がまだ残っていることを意味します。