プロンプトと設定
あなたは大容量エクスポート用の POST /exports を担当しています。リクエストは入力をバリデーションして処理を開始しますが、エクスポートには数分かかる場合があります。面接官は、200、201、202 のどれを返すべきか、そしてクライアントが最終結果をどのように知るのかを尋ねています。
サーバーはエクスポートレコードを永続化し、作業をキューイングできると仮定します。クライアントはタイムアウトしてリクエストを再試行する可能性があります。回答では、単にステータスコードを挙げるだけでなく、コントラクトを定義する必要があります。
面接官がテストしているポイント
- 「リソースが作成された」ことと「リクエストが後続処理のために受理された」ことを区別できているか。
- 永続的なステータスリソース、終端状態、エラー詳細をモデル化できているか。
- リトライによって2つのエクスポートが作成されたり、元のレスポンスが失われたりしないか。
- 分散トランザクションが存在するかのように装うことなく、キューとデータベースの更新が信頼性を保っているか。
回答前の明確化のための質問
- このPOSTは永続的なエクスポートリソースを即座に作成しますか?そうであれば
201 Createdがそのリソースを記述する可能性があります。そうでなければ202で受理されたジョブを確認応答(acknowledge)できます。 - 同じ論理リクエストをリトライできますか?そうであれば、べき等キーまたは呼び出し元から提供されるオペレーションIDを必須にします。
- クライアントはポーリング、Webhook、またはその両方を必要としますか?これにより、ステータス表現と通知コントラクトが変わります。
- エクスポートされたファイルの保持ルールと認可ルールは何ですか?ジョブが完了したからといって、その結果をすべての呼び出し元に公開してよいわけではありません。
30秒で答えるフレームワーク
「私は、処理が遅延され最終結果の準備ができていない場合にのみ 202 Accepted を返します。まず永続的なジョブレコードを作成し、そのステータスURIとオペレーション識別子を返します。クライアントはそのURIをバックオフしながらポーリングするか、認証されたコールバックを受け取ります。べき等キーによって、リトライが同一のジョブおよびレスポンスにマッピングされます。ジョブは queued、running、succeeded、failed などの明示的な状態を遷移します。ワーカーとアウトボックスはリトライ可能であり、ステータスエンドポイントが信頼できる唯一の情報源(source of truth)であり続けます。」
ステップごとの詳細解説
1. リソースのライフサイクルからステータスを選択する
200 OK は、リクエストが表現(representation)を伴って完了したことを意味します。201 Created は、リソースが作成され識別可能であるべきであることを意味します。202 Accepted は、リクエストが受理されたものの、処理が開始または終了していない可能性があることを意味し、最終的な成功を約束するものではありません。
エクスポートレコードが同期的に作成され、それが呼び出し元の管理対象となるリソースである場合は、そのリソースとともに 201 を返すことができます。APIが作業の受理のみを確認し、結果が保留中である場合は、202 にモニター用URIを付与する方が明確です。この選択は、単にキューが存在するという事実ではなく、観測可能なライフサイクルに従う必要があります。
2. レスポンスを実行可能なものにする
オペレーションID、ステータスURL、および state、タイムスタンプ、安全なリトライヒントを含む表現を返します。最小限のレスポンスは次のようになります。
HTTP/1.1 202 Accepted
Location: /exports/exp_123
Retry-After: 5
Content-Type: application/json
{"id":"exp_123","state":"queued","status_url":"/exports/exp_123"}ステータスリソースは、すべての読み取り時に認可を行う必要があります。queued と running は非終端状態です。succeeded には有効期間の短いダウンロード参照が含まれ、failed にはスタックトレースを漏洩させることなく、安定したエラーコードと修復ヒントが含まれます。クライアントは、保持期間を過ぎた後にリソースが消滅することを許容しなければなりません。
3. リトライを収束させる
作業を作成するオペレーションには Idempotency-Key を必須とします。関連するリクエストのハッシュ、生成されたジョブID、レスポンスステータスを永続化します。同一リクエストでキーが再送された場合は元の結果を返し、同一キーで異なるリクエストの場合はクライアントエラーとします。遅延したリトライがウィンドウ外に到達する可能性があるため、時間ウィンドウ単体をアイデンティティルールとして使用してはいけません。
APIは2つの異なるエクスポートに対して2つの異なるキーを受け入れることができます。べき等性は、1つの論理オペレーションに対する重複作業を防ぐものであり、ワーカーを厳密に1回(exactly-once)にするものではありません。
4. データベースとキューのギャップを埋める
エクスポートのレコードとアウトボックイベントを1つのデータベーストランザクションで書き込みます。リレーが保留中のアウトボックスレコードを発行し、ブローカーが確認応答した後にそれらを配信済みとしてマークします。クラッシュにより同一イベントが再度発行される可能性があるため、コンシューマーはエクスポートIDをべき等キーとして使用します。これにより、データベースとブローカーがアトミックにコミットすると主張することなく、コミットされたジョブが最終的に検出可能になるという不変条件が維持されます。
ワーカーは、例えば queued -> running -> succeeded|failed のような条件付き遷移で状態を更新します。古いリトライによって succeeded が running に戻ることはありません。メトリクスでは、キュー滞留時間、実行時間、終端失敗率、アウトボックス遅延を公開する必要があります。
5. ポーリング、コールバック、キャンセルを定義する
ポーリングで条件付きリクエストが使用できるように、ステータスエンドポイントは ETag またはバージョンをサポートします。クライアントはサーバーヒントを用いた指数バックオフを適用し、終端状態に達した後はポーリングを停止します。Webhookは最適化のための手段であり、結果を知る唯一の方法ではありません。配信が失敗する可能性があるため、クライアントはステータスリソースを読み取ることで調整(リコンシリエーション)を行う必要があります。
キャンセルは、POST /exports/exp_123/cancel などの独立したコマンドです。キャンセル可能な状態に対してのみ受け入れられ、それ自体がべき等です。すでに succeeded に達したジョブを遅れて届いたキャンセルでロールバックすることはできません。
質の高い回答例
私ならまず、エクスポートレコードがこの呼び出しによって作成されるリソースであるかどうかを尋ねます。もしそうであれば、201 とそのレコードを返すかもしれません。結果の準備ができていない遅延処理の場合は、オペレーションIDと認証付きステータスURLとともに 202 を返します。べき等キーを必須とし、リクエストフィンガープリントとジョブIDを保存して、リトライに対しては同一の表現を返します。
トランザクションはエクスポートレコードとアウトボックスイベントを一緒に書き込みます。リレーとべき等なコンシューマーが少なくとも1回(at-least-once)の配信を処理します。ステータスのステートマシンは単調増加(queued、running、その後 succeeded または failed)です。クライアントは条件付きリクエストとバックオフを用いてポーリングし、Webhookはあくまでアクセラレーターとして扱います。キュー滞留時間、アウトボックス遅延、終端エラーをメトリクスとして公開し、保持期間、認可、ダウンロード期限、キャンセルを個別に定義します。202 は受理を確認するものであり、成功を保証するものではありません。
よくある間違い
- 間違い:
202を作業が成功することの証拠として扱う → なぜ失敗するか: RFCのセマンティクス上、処理が失敗したり開始すらされなかったりすることが許容されているため → 修正方法: 終端の失敗および保持動作を公開する。 - 間違い: モニターURLなしで
202のみを返す → なぜ失敗するか: クライアントが推測なしに状態を把握できないため → 修正方法: 認証付きステータスリソースとオペレーションIDを返す。 - 間違い: データベースコミット後のキュー発行に依存する → なぜ失敗するか: クラッシュにより、どのワーカーからも認識できないジョブが作成されるため → 修正方法: トランザクショナルアウトボックスとリプレイ可能なリレーを使用する。
- 間違い: キューによって厳密に1回(exactly-once)の実行が保証されると仮定する → なぜ失敗するか: リトライやクラッシュによって重複配信が発生する可能性があるため → 修正方法: コンシューマーをべき等にし、状態遷移を条件付きにする。
- 間違い: べき等キーのすべての再利用に対して成功を返す → なぜ失敗するか: キーによって変更されたリクエストが隠蔽される可能性があるため → 修正方法: リクエストフィンガープリントを比較し、不一致を拒否する。
フォローアップの質問と回答
このエンドポイントは代わりに201を返すべきですか?
同期呼び出しが永続的なエクスポートリソースを作成し、Location で識別できる場合は 201 を返します。意味のある結果が遅延され、レスポンスが受理の確認のみを行う場合は 202 を返します。一部のAPIでは保留状態を公開しながらジョブリソースに対して 201 を使用できますが、そのステータスがどのリソースを記述しているかをドキュメント化してください。
クライアントがまったくポーリングしなかったらどうなりますか?
ドキュメント化された保持期間中はジョブ状態を永続化し、オプションで認証付きWebhookを送信し、後からオペレーションIDによる GET を許可します。コールバックが失敗しても、唯一のステータス確認パスを削除してはいけません。クライアントが数日後に戻ってきた場合でも、期限切れのダウンロードURLや認可チェックが適用されます。
ワーカーはジョブを2回更新できますか?
はい、配信は通常少なくとも1回(at-least-once)です。一意のエクスポートID、条件付き状態遷移、べき等な出力書き込みを使用してください。重複した succeeded イベントは無害であるべきです。終端状態から running への逆遷移は拒否してログに記録する必要があります。