ComfyUIのQueueは「GPUを1本の列に並ばせる装置」であり、アプリ側はその列に対して自分のJob IDで状態を追い、待機中はPOST /queue、実行中はPOST /interruptで止め、失敗は原因別に再試行の可否を決めます。この記事では、複数生成を順番どおり安全に処理するためのQueue制御、Job IDの対応付け、状態遷移、キャンセル、再試行、負荷制御の設計をまとめます。
情報確認日:2026年8月22日(日本時間)
結論:IDを自分で持ち、状態は/queueと/historyから観測し、再試行は原因で分ける
責任範囲
- ComfyUIのQueueが何を守っていて、なぜアプリ側で並列に投げてはいけないかを説明する
- ComfyUIの
prompt_idと自アプリのJob IDを対応させる台帳の形を示す - Pending/Running/Succeeded/Failed/Cancelledの遷移と、それぞれを観測するエンドポイントを整理する
- キャンセルと再試行を「待機中か実行中か」「原因は何か」で分ける設計を示す
- ユーザー単位の上限、Queue上限、backpressureの置き場所を決める
- WebSocketイベントの詳細とGPU最適化は扱わず、別記事に任せる
API経路の全体像とPOST /promptで1枚生成する最小サンプルはComfyUI APIの使い方で扱っています。この記事は、その先にある「複数件を順番管理する」部分だけを担当します。
Queueはなぜ必要か
ComfyUIのServer APIは、受け取ったworkflowを検証したあと実行Queueに積み、サーバー側が順番に処理します。アプリから10件同時にPOST /promptしても、GPUで同時に走るのは基本的に1件で、残りは待機列に並びます。つまり「並列実行」はComfyUIの中には存在せず、並べているだけです。
Queueが必要な理由は、GPUメモリが共有資源だからです。1件のworkflowがVRAMをほぼ使い切ることも多く、2件を同時に走らせると両方が落ちる可能性があります。待たせることは欠点ではなく、落とさないための仕組みです。
| 観点 | ComfyUIのQueueが守るもの | アプリ側で決めること |
|---|---|---|
| 同時実行数 | サーバーが1件ずつ処理する | いくつまで列に入れるか |
| 順序 | 投入順。front指定で先頭に割り込める |
誰の依頼を先に通すか |
| 識別子 | prompt_idとnumber(列の位置) |
自アプリのJob IDと誰が依頼したか |
| 失敗の記録 | /historyに結果が残る |
再試行するか、利用者にどう伝えるか |
公式のルート一覧(2026年8月時点)では、GET /queueがqueue_runningとqueue_pendingを返し、POST /queueがdelete配列とclearを受け取ります。アプリ側のQueue制御は、この2つと/historyの読み取りだけで組めます。
Job IDをどう紐づけるか
ComfyUIが返すprompt_idをそのまま自アプリの主キーにしないでください。理由は二つあります。prompt_idは送信が成功して初めて手に入るため、送信前の状態(受付済み・検証中)を表せません。また、再試行するとprompt_idは新しくなるため、1つの依頼に複数のprompt_idが対応します。
自アプリのJob IDを先に発行し、ComfyUIへの送信ごとにprompt_idを「試行」として紐づける台帳にします。
{
"job_id": "job_20260822_0001",
"user_id": "u_123",
"status": "running",
"created_at": "2026-08-22T09:00:00+09:00",
"workflow_preset": "sdxl-portrait-v3",
"params": { "prompt": "...", "seed": 12345, "width": 1024, "height": 1024 },
"attempts": [
{ "attempt": 1, "prompt_id": "a1b2c3...", "result": "failed", "reason": "oom" },
{ "attempt": 2, "prompt_id": "d4e5f6...", "result": null, "reason": null }
]
}
client_idはPOST /promptのbodyに入れる任意の識別子で、Queue管理だけなら必須ではありませんが、ワーカー名を入れておくと「どのワーカーが送ったか」を後から追えます。
POST /promptの応答に含まれるprompt_idとnumberは、受け取った瞬間に台帳へ書きます。ここで保存に失敗すると、ComfyUI側では走っているのにアプリ側では追えない「迷子Job」が生まれます。
状態遷移をどう管理するか
状態はアプリ側で定義し、ComfyUI側の観測結果から更新します。ComfyUI自体は「走っている」「待っている」「履歴にある」の3区分しか持たないためです。
| アプリ側の状態 | 意味 | 何を見て判定するか |
|---|---|---|
| Pending | 受付済み。まだComfyUIに送っていない | 台帳のみ。アプリ側の上限待ち |
| Queued | 送信済みで待機列にいる | POST /prompt成功後、GET /queueのqueue_pendingにprompt_idがある |
| Running | GPUで実行中 | GET /queueのqueue_runningにprompt_idがある |
| Succeeded | 出力が得られた | GET /history/{prompt_id}にoutputsが入っている |
| Failed | 検証エラーか実行時エラー | POST /promptのnode_errors、または/historyのstatusにエラー情報 |
| Cancelled | 利用者または運用者が止めた | アプリ側の操作記録。/queueから消えたことで確認 |
遷移の順序は Pending → Queued → Running → Succeeded | Failed で、CancelledはQueuedとRunningのどちらからでも入れます。逆方向の遷移(SucceededからRunningなど)はあり得ないため、台帳更新時にこの順序を検査すると、ポーリングの遅延や二重受信で状態が巻き戻る不具合を防げます。
観測の方法はポーリングでも十分です。進捗の割合やノード単位のイベントが必要ならComfyUIの生成進捗をWebSocketで取得する|Queue・Node・完了通知を理解するを使いますが、Queue制御そのものはHTTPの読み取りだけで完結します。
5件を投入して遷移を記録する
設計が正しいかは、実際に5件を投入して「待機→実行→取消→再試行」を記録すると判断できます。数値は環境で変わるため、記録表は空欄のまま載せます。
- 1
同じworkflowを5件連続で送る
seedだけ変えて
POST /promptを5回。応答のprompt_idとnumberを台帳に書く - 2
1秒ごとに
GET /queueを読むqueue_runningとqueue_pendingに含まれるprompt_idを時刻付きで記録する - 3
3件目を待機中に取り消す
POST /queueに{"delete": ["<3件目のprompt_id>"]}を送り、次の読み取りでqueue_pendingから消えたか確認する - 4
4件目を実行中に中断する
queue_runningに4件目が入った直後にPOST /interruptを送り、/historyの記録を確認する - 5
3件目を再試行する
同じparamsで再送し、新しい
prompt_idを試行2として台帳に追加する
| Job | prompt_id(試行1) | Queued時刻 | Running時刻 | 終了時刻 | 終了状態 | prompt_id(試行2) |
|---|---|---|---|---|---|---|
| 1 | — | |||||
| 2 | — | |||||
| 3(取消→再試行) | — | Cancelled | ||||
| 4(中断) | — | |||||
| 5 | — |
見るべき点は三つです。Running時刻が重ならないこと、取り消した3件目がRunningに入らないこと、中断した4件目のあとで5件目が自動的にRunningに進むことです。
キャンセルをどう分けるか
利用者が押す「取消」ボタンは一つでも、裏側の処理は二つに分かれます。待機中と実行中でComfyUIに送るリクエストが違うからです。
待機中(Queued)の取消
POST /queueに{"delete": ["<prompt_id>"]}を送る- GPUにはまだ乗っていないため、即座に列から外れる
- アプリ側の状態はCancelledにして、
/historyには残らない前提で扱う
実行中(Running)の中断
POST /interruptを送る- 対象は「いま実行中のworkflow」であり、
prompt_idを指定する引数は公式ルート一覧に記載がない - 送る直前に
GET /queueのqueue_runningが自分のprompt_idか確認してから送る
interruptは他人のJobも止める:複数の利用者が同じComfyUIを共有している場合、POST /interruptは誰のJobであっても実行中のものを止めます。「自分のJobがRunningであることを確認してから送る」「確認から送信までの間に入れ替わる可能性を受け入れ、送信後にもう一度/queueを読んで結果を検証する」の二段構えにしてください。
UI側では、取消ボタンを押した瞬間に「Cancelling」にして二重送信を防ぎ、ComfyUI側の確認が取れてから「Cancelled」に確定させます。
再試行をどう設計するか
再試行の原則は「原因を見てから決める」です。失敗の種類によって、同じ条件で再送しても意味がないものと、時間を置けば通るものがあります。
| 失敗の種類 | どこで分かるか | 再試行 |
|---|---|---|
| 検証エラー(ノード未定義・入力不正) | POST /promptがerrorとnode_errorsを返す |
しない。workflowかparamsを直す |
| VRAM不足(OOM) | /historyのエラー情報、サーバーログ |
同条件では再送しない。解像度やBatchを下げて1回だけ |
| 接続失敗・タイムアウト | HTTPリクエスト自体が失敗 | 間隔を空けて上限回数まで |
| 中断(interrupt) | アプリ側の操作記録 | 利用者の意思で止めたため自動再試行はしない |
特に避けたいのは、OOMを「一時的な失敗」と誤分類して同条件で無限に再送することです。GPUメモリは同じworkflowなら同じだけ必要なので、再送しても同じ場所で落ち、その間ほかのJobを待たせ続けます。OOMの切り分けはComfyUIのVRAM不足を直すに従い、再試行は条件を変えたときだけにします。
def should_retry(reason: str, attempt: int) -> bool:
if attempt >= 3 or reason in ("validation_error", "cancelled", "oom"):
return False # oom は params を縮小してから別Jobとして投入する
return reason in ("connection_error", "timeout")
再試行の間隔は固定秒数ではなく、queue_pendingの長さを見て「列が空くまで待つ」方が実態に合います。
大量生成の負荷をどう制御するか
ComfyUIのQueueは「入ってきたものを全部並べる」だけで、誰がいくつ入れたかは見ません。公平性と安全性はアプリ側の責任です。制御点は三つあります。
- 1
ユーザー単位の上限
同時にQueued+Runningでいられる件数を利用者ごとに決める(例:3件)。超えた分はPendingで待たせるか、受付時に断る
- 2
Queue全体の上限
GET /queueのqueue_pendingの長さがしきい値を超えたら新規送信を止める。ComfyUI側には上限機能がないため、アプリ側で門番を置く - 3
backpressure(受け手からの圧力)
上限に達したら呼び出し元にHTTP 429などで「いまは受けられない」と返し、待ち時間の目安を添える。黙ってPendingに溜めると、利用者は止まっているのか分からない
上限の数値は環境で決まります。VRAM、1件あたりの平均所要時間、許容できる待ち時間の三つから逆算してください。例に挙げた「3件」は説明のための仮置きで、最適値ではありません。判定は送信直前にGET /queueを読み、queue_runningとqueue_pendingの合計がしきい値以上なら送らない、という一つの関数に閉じ込めると見通しがよくなります。
認証やLAN公開を含めた外部からの保護、Next.jsなどのフロントエンドに組み込む形は別記事で扱います。
よくある質問
ComfyUIを2プロセス起動すれば並列実行できますか?
ポートを分ければ起動できますが、同じGPUを共有するためVRAMが足りなければ両方が落ちます。GPUが2枚あるか、1件あたりのVRAM使用量が半分以下に収まる場合だけ検討してください。
front: trueで割り込めば優先度制御になりますか?
POST /promptのfrontは列の先頭に入れる指定で、簡易的な優先度として使えます。ただし連発すると後ろのJobが永遠に進まないため、「管理者の緊急1件だけ」のように使う場面を限定してください。
取り消したJobは/historyに残りますか?
待機中に/queueのdeleteで外した場合は実行されていないため、履歴には基本的に残りません。実行中に/interruptで止めた場合の記録内容はバージョンで差があり得るため、アプリ側の操作記録を正とし、履歴の有無には依存しない設計にしてください。
まとめ
ComfyUIのQueueは1件ずつ処理する列であり、アプリ側の仕事はその列に対して自分のJob IDで状態を追うことです。prompt_idは試行ごとに紐づけ、状態はGET /queueと/historyから観測し、取消は待機中と実行中で分け、再試行は原因別に可否を決めます。
負荷制御はComfyUIにはないため、ユーザー上限・Queue上限・429応答の三つをアプリ側に置きます。まずは5件投入の記録表を埋めて、自分の環境での遷移を確認してから上限値を決めてください。