0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

非同期の音楽生成APIでタイムアウトしたら:タスクIDと冪等キーで復旧経路を分ける

0
Posted at

Kunavo(AI APIゲートウェイ)の運営チームです。コード・執筆・翻訳にAIを使用しています。この記事では、自作の小さなオフライン判断ロジックを10件のテストで確認しました。実サービスへの生成リクエスト、顧客ログ、音楽作品は使っていません。

非同期の音楽生成では、POSTがタイムアウトしても「生成されなかった」とは限りません。サーバー側では受付済みで、応答だけが届かなかった可能性があります。この状態で新しいキーを発行して再送すると、別の生成として扱われる可能性があります。

ここではPythonの標準ライブラリだけで、復旧時の「次に何をするか」を確認します。HTTPクライアントや永続キューの完成版ではありません。

1. 先に残すのは、結果ではなく依頼の記録

最初のPOSTより前に、次の情報を永続化します。

  • 業務上の操作IDと利用アカウント・環境
  • その生成依頼に割り当てた冪等キー
  • 送信する本文と、そのハッシュ
  • 最初の送信開始時刻
  • 応答を受けた後のタスクID、状態、出力情報

API認証キーはこの記録に入れません。歌詞を含む本文は非公開情報になり得るため、保管先の権限と保存期間も決めます。

新しい曲を作るという判断と、同じ依頼の通信再試行を分けるのがポイントです。前者は新しい操作、後者は同じアカウント・キー・本文を維持します。

実装のprepare()はメモリ内の記録を作るだけです。実ワーカーでは、送信前にDB等への保存を完了させ、同一操作を複数ワーカーが同時実行しないようにしてください。

2. 「タスクIDがあるか」で復旧経路を分ける

状態 次の判断
POSTの応答を失い、IDが不明 有限の復旧期間内で、元のキーと同じ本文を使用
IDが判明している そのIDをGETする。新規POSTはしない
failed エラーを調べる。別の生成は明示的に決める
completed、archived=false 一時URLのまま完了扱いにせず、保存処理を手配
IDが不明なまま復旧期間を超過 照合・手動確認へ。自動でキーを更新しない
ローカルのポーリング回数上限 IDを保持して待機を中断。サーバーの処理取消とは区別

Kunavoの公開仕様では、音楽の非同期受付はPOST /v1/audio/music/jobs、状態確認はGET /v1/audio/music/jobs/{id}です。新規受付はHTTP 202、冪等キーによる再取得は200を返します。どちらも本文の状態を確認します。

今回のロジックは、IDが分かっていれば再送期間を過ぎてもGETを選びます。POSTを再送できる期間と、既存タスクを照会する期間を同じ条件にしないためです。GETが404なら、新しく生成する前にアカウント、ID、結果の保存期間などを確認します。

3. 冪等キーを万能な保証と考えない

公開ドキュメントは再送期間をおおむね24時間と説明しています。一方、このサンプルはアプリ側の保守的な判断として1時間で不明な送信を照合対象にします。1時間はサーバーのTTLではありません。再試行のたびに開始時刻を更新すると上限が無意味になるため、最初の時刻を保持します。

if fingerprint(state["payload"]) != state["payload_sha256"]:
    return {"action": "manual_review", "reason": "payload_changed"}

同じキーで本文だけを変える操作は、ローカル側で止めます。これは「サーバーが必ず本文の不一致を拒否する」という主張ではありません。

また、上流の受付とローカル記録の保存の間には障害が起き得ます。冪等キーがあるからといって、あらゆる障害下のexactly-once実行や重複課金防止を保証できるわけではありません。

4. ポーリングの停止は、生成の失敗ではない

サンプルの待ち時間は5、10、20、30秒、以後30秒です。12回の正常な待機応答を観測したらpause_pollingを返します。ここで元のIDを捨てて再生成してはいけません。停止したのはクライアントの待機であり、リモート処理は継続している可能性があります。

この12回は通信エラーを含めた総試行上限ではありません。実ワーカーにはHTTPタイムアウト、通信エラー用の別上限、jitter、Retry-Afterへの対応が必要です。401/403や入力検証エラーを無条件に再試行する設計にもしていません。

5. 生成完了と保存完了を分ける

completedでも、output.archivedと出力URLを確認します。archived=falseはCDN保存へのフォールバックを確認する必要がある状態です。一時的なURLであれば、自分の保存先へのコピーを手配します。

このコードはダウンロードしません。archive_outputsという次の作業を返します。record_manifestもメタデータを記録できるという意味で、ローカルバックアップ済み、楽曲の採用済み、公開権利の確認済みを意味しません。

Webhookを使う場合は、署名・鮮度の検証、イベントIDでの重複処理防止が別途必要です。今回の例はポーリングの判断だけを対象としています。

6. オフラインで失敗パターンを確認する

コード、README、テスト

Python 3.9以上、外部パッケージとAPIキーは不要です。リポジトリのルートから実行します。

python3 music-job-recovery/recovery.py
python3 -m unittest discover -s music-job-recovery -p 'test_*.py' -v

確認結果は10件成功。応答を失った後もキーと本文が変わらないこと、既知IDはGETを選ぶこと、失敗時に自動で新規生成しないこと、本文の変更、別タスクの応答、不完全な出力、終端後の矛盾した応答も検査しています。

このテスト結果から分かるのは、ローカルの判断が想定どおりかという点です。サーバー側の冪等性、実際の請求、音質、サービスの安定性は未検証です。

参考資料

まずは「受付後に応答だけを失う」ケースを自分のワーカーで再現し、保存済みの同じ操作へ戻れるかを確かめると、通常の成功例だけでは見えない復旧経路を確認できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?