AI画像生成は、一見すると単純なリクエスト・レスポンスの機能に見えます。
ユーザーがプロンプトを入力し、生成ボタンを押し、画像が返ってくるのを待つ。
プロトタイプであれば、この形でも十分に動きます。ただし、本番環境のプロダクトとして扱うと、この設計はすぐに脆くなります。
画像生成には数秒から数分かかることがあります。プロバイダーによっては、最初にジョブIDだけを返し、最終結果は後で返します。結果がWebhookで届く場合もあれば、こちらからポーリングする必要がある場合もあります。リクエストは失敗したり、タイムアウトしたり、ユーザーがページを離れた後に完了したりします。
そのため、AI画像生成は非同期ワークフローとして設計した方が扱いやすくなります。
この記事では、Image 2 を開発する中で整理した、AI画像生成を非同期化する理由と基本的な設計をまとめます。
単純な実装
もっとも直接的な実装は、次のような形です。
User -> API route -> AI provider -> result -> user
理解しやすい構成ですが、いくつか問題があります。
- HTTPリクエストがタイムアウトする可能性がある
- リトライによって重複ジョブが作られる可能性がある
- フロントエンド体験がプロバイダーの応答時間に依存する
- 課金やクレジット消費の保護が難しくなる
- 生成された画像が一時的なプロバイダーURLに依存する
- リクエスト終了後に失敗状態を修復しにくい
この形はデモには向いています。しかし、実ユーザー、決済、ストレージ、リトライが関わると、安定して運用するのが難しくなります。
より扱いやすい形
より堅牢にするには、ユーザーからのリクエストと実際の生成処理を分離します。
User request
|
v
Create generation record
|
v
Push message to queue
|
v
Background worker submits job
|
v
Webhook or polling gets result
|
v
Store asset and update status
ユーザー向けのAPIは、生成タスクを作成してすぐに返します。UI側では queued、processing、completed、failed のような状態を表示します。
時間のかかる処理はバックグラウンドで進めます。
非同期化で何が良くなるか
非同期ワークフローにすると、システムが失敗から回復しやすくなります。
プロバイダーの応答が遅い場合、タスクは processing のまま維持できます。
プロバイダー側で失敗した場合、タスクを failed に更新し、必要であればクレジットを戻せます。
Webhookを受け取れなかった場合でも、後からスケジュールジョブでポーリングできます。
Webhookとポーリングの両方が同じ最終結果を見た場合でも、重複した確定処理を無視できます。
最後の点は本番環境では重要です。同じ生成結果が複数回観測されることはあります。completed や failed のような終端状態への更新は、冪等にしておく必要があります。
小さな状態モデル
最初から複雑なステートマシンを作る必要はありません。小さなモデルでも十分に始められます。
created -> queued -> processing -> completed
|
-> failed
それぞれの状態は、明確な意味を持たせます。
-
created: リクエストを受け付けた -
queued: バックグラウンド処理を予約した -
processing: プロバイダー側のジョブが開始された -
completed: 最終的な画像アセットが利用可能になった -
failed: タスクを完了できない
重要なのは、終端状態を保護することです。一度 completed または failed になったタスクに対して、リトライや重複コールバックが同じ結果を再適用しないようにします。
生成結果は自分たちのストレージに保存する
多くのAIプロバイダーは、生成画像のURLを返します。ただし、そのURLは一時的なものだったり、プロバイダー側の管理下にあったりします。
本番プロダクトでは、最終結果を自分たちのストレージにコピーした方が扱いやすくなります。
Provider result URL -> app storage -> stable asset URL
Cloudflareを使う場合であれば、最終画像をR2に保存し、自分たちのCDNドメインから配信する構成が考えられます。
これにより、次のような処理を管理しやすくなります。
- ユーザー所有権の確認
- ダウンロード
- クリーンアップ
- モデレーション
- 安定したプレビューURL
- 課金履歴との紐付け
AIプロバイダーは画像を生成します。一方で、画像をプロダクト内でどう扱うかはアプリケーション側が責任を持つべき領域です。
複数モデル対応ではさらに効いてくる
複数のモデルや生成方式を扱うアプリでは、非同期ワークフローの価値がさらに大きくなります。
テキストから画像を作るモデル、画像編集モデル、参照画像を使うワークフローでは、それぞれ挙動が異なります。すぐに結果を返すものもあれば、プロバイダー側のジョブIDが必要なものもあります。高解像度出力に対応しているものもあれば、入力制限が異なるものもあります。
このような差分をフロントエンドに直接漏らすと、UIと状態管理が複雑になります。
一方で、バックエンド側に共通のタスクライフサイクルを持たせておけば、ユーザーにはシンプルな生成体験を提供しつつ、プロバイダーごとの差分を内部に閉じ込められます。
これは、特定のプロバイダーAPIを中心に設計するのではなく、プロダクトのワークフローを中心に設計するという考え方です。
まとめ
AI画像生成は、単なるモデル呼び出しではありません。プロダクトとしては、時間のかかるジョブ、重複コールバック、リトライ、ストレージ、モデレーション、クレジット処理まで含んだワークフローです。
実験であれば同期的なAPIルートでも十分です。本番環境では、非同期アーキテクチャにしておくことで、遅い処理や失敗をより安定して扱えます。
モデルは画像を作ります。ワークフローはプロダクトを信頼できるものにします。