AI活用

ComfyUI APIのQueueを制御する|並列実行・キャンセル・再試行の設計

ComfyUIのQueueは1件ずつ処理する列です。自アプリのJob IDとprompt_idの対応付け、状態遷移、/queueと/interruptで分けるキャンセル、原因別の再試行、ユーザー上限と429によるbackpressureの設計を解説します。

この記事の目次
  1. 結論:IDを自分で持ち、状態は/queueと/historyから観測し、再試行は原因で分ける
  2. Queueはなぜ必要か
  3. Job IDをどう紐づけるか
  4. 状態遷移をどう管理するか
  5. 5件を投入して遷移を記録する
  6. キャンセルをどう分けるか
  7. 再試行をどう設計するか
  8. 大量生成の負荷をどう制御するか
  9. よくある質問
  10. まとめ

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_idnumber(列の位置) 自アプリのJob IDと誰が依頼したか
失敗の記録 /historyに結果が残る 再試行するか、利用者にどう伝えるか

公式のルート一覧(2026年8月時点)では、GET /queuequeue_runningqueue_pendingを返し、POST /queuedelete配列と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_idPOST /promptのbodyに入れる任意の識別子で、Queue管理だけなら必須ではありませんが、ワーカー名を入れておくと「どのワーカーが送ったか」を後から追えます。

POST /promptの応答に含まれるprompt_idnumberは、受け取った瞬間に台帳へ書きます。ここで保存に失敗すると、ComfyUI側では走っているのにアプリ側では追えない「迷子Job」が生まれます。

状態遷移をどう管理するか

状態はアプリ側で定義し、ComfyUI側の観測結果から更新します。ComfyUI自体は「走っている」「待っている」「履歴にある」の3区分しか持たないためです。

アプリ側の状態 意味 何を見て判定するか
Pending 受付済み。まだComfyUIに送っていない 台帳のみ。アプリ側の上限待ち
Queued 送信済みで待機列にいる POST /prompt成功後、GET /queuequeue_pendingprompt_idがある
Running GPUで実行中 GET /queuequeue_runningprompt_idがある
Succeeded 出力が得られた GET /history/{prompt_id}outputsが入っている
Failed 検証エラーか実行時エラー POST /promptnode_errors、または/historystatusにエラー情報
Cancelled 利用者または運用者が止めた アプリ側の操作記録。/queueから消えたことで確認

遷移の順序は Pending → Queued → Running → Succeeded | Failed で、CancelledはQueuedとRunningのどちらからでも入れます。逆方向の遷移(SucceededからRunningなど)はあり得ないため、台帳更新時にこの順序を検査すると、ポーリングの遅延や二重受信で状態が巻き戻る不具合を防げます。

観測の方法はポーリングでも十分です。進捗の割合やノード単位のイベントが必要ならComfyUIの生成進捗をWebSocketで取得する|Queue・Node・完了通知を理解するを使いますが、Queue制御そのものはHTTPの読み取りだけで完結します。

5件を投入して遷移を記録する

設計が正しいかは、実際に5件を投入して「待機→実行→取消→再試行」を記録すると判断できます。数値は環境で変わるため、記録表は空欄のまま載せます。

  1. 1
    同じworkflowを5件連続で送る

    seedだけ変えてPOST /promptを5回。応答のprompt_idnumberを台帳に書く

  2. 2
    1秒ごとにGET /queueを読む

    queue_runningqueue_pendingに含まれるprompt_idを時刻付きで記録する

  3. 3
    3件目を待機中に取り消す

    POST /queue{"delete": ["<3件目のprompt_id>"]}を送り、次の読み取りでqueue_pendingから消えたか確認する

  4. 4
    4件目を実行中に中断する

    queue_runningに4件目が入った直後にPOST /interruptを送り、/historyの記録を確認する

  5. 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 /queuequeue_runningが自分のprompt_idか確認してから送る

interruptは他人のJobも止める:複数の利用者が同じComfyUIを共有している場合、POST /interruptは誰のJobであっても実行中のものを止めます。「自分のJobがRunningであることを確認してから送る」「確認から送信までの間に入れ替わる可能性を受け入れ、送信後にもう一度/queueを読んで結果を検証する」の二段構えにしてください。

UI側では、取消ボタンを押した瞬間に「Cancelling」にして二重送信を防ぎ、ComfyUI側の確認が取れてから「Cancelled」に確定させます。

再試行をどう設計するか

再試行の原則は「原因を見てから決める」です。失敗の種類によって、同じ条件で再送しても意味がないものと、時間を置けば通るものがあります。

失敗の種類 どこで分かるか 再試行
検証エラー(ノード未定義・入力不正) POST /prompterrornode_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. 1
    ユーザー単位の上限

    同時にQueued+Runningでいられる件数を利用者ごとに決める(例:3件)。超えた分はPendingで待たせるか、受付時に断る

  2. 2
    Queue全体の上限

    GET /queuequeue_pendingの長さがしきい値を超えたら新規送信を止める。ComfyUI側には上限機能がないため、アプリ側で門番を置く

  3. 3
    backpressure(受け手からの圧力)

    上限に達したら呼び出し元にHTTP 429などで「いまは受けられない」と返し、待ち時間の目安を添える。黙ってPendingに溜めると、利用者は止まっているのか分からない

上限の数値は環境で決まります。VRAM、1件あたりの平均所要時間、許容できる待ち時間の三つから逆算してください。例に挙げた「3件」は説明のための仮置きで、最適値ではありません。判定は送信直前にGET /queueを読み、queue_runningqueue_pendingの合計がしきい値以上なら送らない、という一つの関数に閉じ込めると見通しがよくなります。

認証やLAN公開を含めた外部からの保護、Next.jsなどのフロントエンドに組み込む形は別記事で扱います。

よくある質問

ComfyUIを2プロセス起動すれば並列実行できますか?

ポートを分ければ起動できますが、同じGPUを共有するためVRAMが足りなければ両方が落ちます。GPUが2枚あるか、1件あたりのVRAM使用量が半分以下に収まる場合だけ検討してください。

front: trueで割り込めば優先度制御になりますか?

POST /promptfrontは列の先頭に入れる指定で、簡易的な優先度として使えます。ただし連発すると後ろのJobが永遠に進まないため、「管理者の緊急1件だけ」のように使う場面を限定してください。

取り消したJobは/historyに残りますか?

待機中に/queueのdeleteで外した場合は実行されていないため、履歴には基本的に残りません。実行中に/interruptで止めた場合の記録内容はバージョンで差があり得るため、アプリ側の操作記録を正とし、履歴の有無には依存しない設計にしてください。

まとめ

ComfyUIのQueueは1件ずつ処理する列であり、アプリ側の仕事はその列に対して自分のJob IDで状態を追うことです。prompt_idは試行ごとに紐づけ、状態はGET /queue/historyから観測し、取消は待機中と実行中で分け、再試行は原因別に可否を決めます。

負荷制御はComfyUIにはないため、ユーザー上限・Queue上限・429応答の三つをアプリ側に置きます。まずは5件投入の記録表を埋めて、自分の環境での遷移を確認してから上限値を決めてください。

スポンサーリンク