プロンプトとスコープ
クライアントが注文作成コマンドを送信します。サーバーは注文をコミットした後に、レスポンスを送信する前に接続が切断される可能性があります。クライアントは結果を知ることができず、同じ Idempotency-Key でリトライします。リクエストコントラクト、永続レコード、並行性制御、およびリカバリフローを設計し、どの障害であればリトライが安全であるかを明示してください。主要なスキルは副作用の境界、アトミック性、および説明可能な障害状態であるため、これはバックエンドに属します。
面接官が評価するポイント
優れた回答では、「リクエストが到着しなかった」、「完了したがレスポンスが失われた」、「まだ処理中」を区別し、リプレイ、ステータス検索、または処理中レスポンスを選択します。また、単に「Redis にキーを格納する」と言うだけでなく、同一キー/異なるパラメータ、並行する初回リクエスト、ストレージ障害、有効期限、マルチリージョンルーティング、およびダウンストリームへの影響も網羅します。
最初に明確にすべき質問
- キーは単一の論理コマンドに対してクライアントが生成するものですか、それともビジネスフィールドからサーバーが導出するものですか?
- どのリクエストフィールドがキーにバインドされ、不一致を表すエラーは何ですか?
- 注文の書き込みと冪等性レコードは単一のデータベーストランザクション内にありますか?決済には独自の冪等性コントラクトがありますか?
- クライアントはどれくらい待機する可能性があり、キーはどれくらい保持されますか?また、期限切れによって2つ目の注文が作成される可能性はありますか?
- サービスは単一リージョンですか、それともマルチリージョンですか?また、すべてのイングレスパスに対してどのストアが信頼できる情報源(オーソリタティブ)ですか?
30秒の回答フレームワーク
「クライアントは1つの論理コマンドに対して1つの安定したキーを作成し、リトライ時にそれを再利用します。サーバーはテナント、オペレーション、およびキーに対して永続的な一意性制約を適用し、リクエストフィンガープリント、状態、および最終レスポンスを保存します。最初のリクエストはアトミックに processing を要求して注文トランザクションを実行します。同一キー/同一パラメータのリクエストは待機またはリプレイされ、不一致は拒否されます。クラッシュ後は、トランザクションおよびダウンストリームの証拠からリカバリします。補償を行う前に不明な副作用を照会し、決して盲目的にリトライしないでください。保持期間はリトライウィンドウをカバーする必要があり、メトリクスによって重複する副作用がゼロであることを証明する必要があります。」
ステップごとの解決策
コントラクトでは、1つの論理コマンドのリトライ全体で変更されない、空でない制限付きのキーを要求する必要があります。サーバーは、正規化されたリクエストハッシュ、状態、リソース ID、レスポンスコード、レスポンスボディ、および有効期限とともに、一意の (tenant_id, operation, idempotency_key) を保存します。ハッシュにより、異なるコマンドに対して1つのキーが誤って再利用されるのを防ぎます。
最初のリクエストでは、永続的な processing レコードを挿入し、単一のローカルトランザクションで注文を作成するか、両者を明確にリンクするトランザクションログを使用する必要があります。一意性の競合が発生した場合は、既存のレコードを読み取ります。succeeded の場合は保存されたレスポンスをリプレイし、failed の場合は同じビジネスエラーを返し、processing の場合は少し待機するか処理中を返します。インメモリロックのみでは、再起動時やレプリカ間で障害が発生します。
並行性は、一意性制約と条件付き更新によって決定されます。作成レコードを所有するリクエストのみが processing を succeeded に移行できます。2つのワーカーがコミットできないように、更新にはバージョンまたは期待される状態を含めます。プロセスのクラッシュ前に注文トランザクションが成功した場合、リトライは succeeded を読み取ってリプレイします。トランザクションがロールバックされた場合、リトライは安全に実行できます。
ダウンストリームの副作用により、リモートのエフェクトが成功している可能性がある一方でローカルの結果が不明であるという時間枠が生じます。決済、配送、およびメッセージングでは、ダウンストリームの冪等性コントラクトとともに同じビジネスオペレーション ID を使用し、リクエストと結果を永続化する必要があります。ダウンストリームの冪等性がない場合は、照合をクエリするか、アウトボックスを介して公開してワーカーからリトライします。ローカル呼び出しがタイムアウトしたというだけで、決して再度請求を行わないでください。
処理中(in-progress)のレコードにはリカバリが必要です。リースとハートビートを保存します。リカバリワーカーは、終端状態に進める前に、注文トランザクション、ダウンストリームのステータス、またはトランザクションログを確認します。証拠が不十分な場合は、失敗として扱うのではなく unknown をマークして照合に送信します。ステートマシンは succeeded を processing に戻してはなりません。
有効期限はビジネスリスクと一致させる必要があります。クライアントの最大リトライ、ネットワークリトライキュー、および補償ウィンドウ全体にわたってレコードを保持します。期限切れのキーを再利用した場合は、暗黙的に2つ目の注文を作成するのではなく、明示的な key_expired を返す必要があります。古いレスポンスボディは、監査ダイジェストとリソースレベルの一意性が維持されている場合にのみ圧縮できます。
マルチリージョン展開では、論理キーを1つのオーソリタティブなストアにルーティングするか、同期レプリケーションによってグローバルな一意性制約を適用します。processing の読み取りが遅延しているという理由だけで、別のレプリカで再度実行しないでください。キーの競合、パラメータの競合、処理中タイムアウト、リプレイレスポンス、不明な状態、重複リソースのブロック、および照合の差異を計測します。
質の高い模範解答
「私は冪等性キーを、HTTP リトライごとの新しいリクエスト ID ではなく、1つの論理的な作成コマンドの安定した識別子として定義します。サーバーはテナント、オペレーション、およびキーに対して永続的な一意性制約を適用し、リクエストハッシュ、状態、リソース ID、レスポンス、および有効期限を保存します。最初のリクエストはアトミックに processing を要求して注文を作成します。同一キー/同一パラメータのリクエストはリプレイまたは待機し、不一致は拒否されます。
私は注文の書き込みと冪等性レコードを1つのトランザクション内に保持し、決済やその他のダウンストリームの副作用に同じオペレーション ID を渡します。レスポンスが失われた場合、リトライは保存された結果を読み取ります。ローカルの状態が不明な場合、別の POST で推測するのではなく、ダウンストリームおよび照合レコードをクエリします。リースされたワーカーがスタックした processing をリカバリし、条件付き遷移は succeeded、failed、または unknown にのみ遷移します。保持期間はリトライと補償をカバーします。並行する初回リクエスト、プロセスクラッシュ、レスポンスの喪失、リージョン間の遅延を注入し、注文や請求の重複がゼロであることを検証します。」
よくある間違い
- リトライごとに新しいキーを生成する → サーバーが1つの論理コマンドを識別できない → 元のキーを再利用する。
- インメモリまたは単一ホストのロックのみを使用する → 再起動やレプリカによって重複排除が失われる → 永続的な一意性を強制する。
- 異なるペイロードに対して結果をリプレイする → クライアントのバグを隠蔽してしまう → フィンガープリントを保存して不一致を拒否する。
processingを記録した直後にダウンストリームを呼び出す → クラッシュによってエフェクトが認識不能になる → トランザクション、アウトボックス、またはダウンストリームの冪等性を使用する。- タイムアウト後に再度請求を行う → リモートの請求が成功している可能性がある → まずクエリを実行して照合する。
- スタックしたレコードを失敗として扱い再実行する → 2つ目のリソースを作成してしまう → リカバリ時に証拠を収集する。
- キーの有効期限が早すぎる → 遅延したリトライによって重複が作成される → リスクおよびリトライウィンドウと保持期間を合わせる。
- 直列呼び出しのみをテストする → 競合によって二重書き込みが発生する → 同一キーの並行性、クラッシュ、およびマルチリージョンルーティングをテストする。
フォローアップの質問と回答
フォローアップ1:なぜ注文 ID を一意のキーとして使用しないのですか?
注文 ID は通常、サーバーがリソースを作成した後にのみ存在するため、最初のレスポンス前の時間枠をカバーできません。冪等性キーは副作用の前に存在し、リトライを1つの論理コマンドにバインドします。
フォローアップ2:同じキーでペイロードが変更された場合はどうなりますか?
正規化されたボディと関連ヘッダーをハッシュ化します。異なるフィンガープリントに対しては、新しい副作用を発生させることなくパラメータ競合エラーを返します。クライアントは新しいコマンドのために新しいキーを作成する必要があります。
フォローアップ3:最初のリクエストが永遠に processing のままになったらどうしますか?
リース、ハートビート、およびタイムアウトスキャンを使用します。リカバリワーカーがローカルトランザクション、ダウンストリームの結果、およびメッセージログを確認します。十分な証拠がある場合のみ状態を進め、そうでない場合は照合のために unknown とします。
フォローアップ4:Redis を唯一の冪等性ストアにすることはできますか?
データベースが副作用を所有している場合、Redis のエビクション、障害、またはレプリケーション遅延によって安全境界が失われる可能性があります。Redis は短期間の作業の調整には使用できますが、最終状態と一意性はビジネスの書き込みと整合性のある永続ストレージに属します。
フォローアップ5:キーの有効期限が切れた後は何が起こるべきですか?
古いキーを暗黙的に受け入れてはいけません。有効期限切れエラーを返し、クライアントに元の注文を照会するか新しいコマンドを作成するよう案内します。リソースレベルのビジネスの一意性によって、別のガードを提供する必要があります。
フォローアップ6:重複する副作用が存在しないことをどのように証明しますか?
同じキーを並行して送信し、コミットの前後でプロセスを終了させ、レスポンスを喪失させ、ダウンストリーム呼び出しをタイムアウトさせます。リソースの一意性、オペレーション ID、リプレイされたレスポンス、状態遷移ログ、および照合を確認します。アサーションは、論理キーごとに最大1回の成功した副作用であることです。
フォローアップ7:冪等性キーは exactly-once(正確に1回)の処理ですか?
いいえ。これは単一のサービスが重複コマンドを認識できるようにするものであり、サービス間のネットワークを exactly-once にするものではありません。トランザクション、アウトボックス配信、ダウンストリームの冪等性、クエリ、および照合が依然として必要であり、明示的な unknown の結果を伴います。