ページ

2026年6月12日金曜日

LLMの重い処理はキューに逃がす:RQDB4AIとWebポーリングで作る実運用向けAIジョブ基盤

LLMの重い処理はキューに逃がす:RQDB4AIとWebポーリングで作る実運用向けAIジョブ基盤

LLMを使うWeb機能を作るとき、最初はフォーム送信の中でそのままAI APIやローカルLLMを呼びたくなります。

たとえば、URLを入力してAIに要約させる。X投稿を読み込んで考察ブログを作る。OSSリポジトリを分析して紹介文を生成する。処理自体はシンプルですが、実運用ではすぐに問題が出ます。

  • 生成に1〜3分かかる
  • HTTPリクエストがタイムアウトする
  • ユーザーが画面を閉じると状態が分からない
  • 途中で失敗した理由が残らない
  • 複数ジョブを同時に投げるとWebサーバが重くなる
  • 完了したのか、キューに入っただけなのかが曖昧になる

今回、URL2AIの oss.phpainews.phpaitech.phpustory.php で、この問題を RQDB4AI + Webポーリング の形に整理しました。

結論から言うと、LLMを使う時間のかかる処理は、Web画面で同期的に待つより、キューに投げて、画面は状態をポーリングする構成がかなり良いです。


なぜLLM処理を同期実行しない方がいいのか

LLM処理は、普通のCRUDとは性質が違います。

データベースに1行INSERTするだけなら、HTTPリクエスト内で完了させても問題ありません。しかしLLMを呼ぶ処理は、外部API、ローカルOllama、Claude CLI、Codex CLI、スクレイピング、保存処理、SNS告知などが連鎖します。

処理時間も読みにくいです。

  • Ollamaが混んでいる
  • Claude/Codex CLIの起動に時間がかかる
  • 対象URLの取得が遅い
  • X投稿や記事本文の取得に失敗する
  • 保存先CMSやSNS投稿APIが一時的に不安定になる

このような処理をWebのPOSTリクエストで最後まで待つと、UIもサーバも脆くなります。

ユーザーから見ると「ボタンを押したのに固まった」に見えます。サーバから見ると「長時間のPHPプロセスが残る」状態になります。さらに失敗時に、どこまで進んだのかも追いにくくなります。


RQDB4AIの役割

そこで使っているのが RQDB4AI です。

RQDB4AIは、AI処理や自動化処理をPython callableとしてキュー実行するための軽量なジョブ基盤です。内部的にはRedis/RQの考え方に近く、Webやスケジューラからジョブを投入し、workerが非同期で実行します。

重要なのは、RQDB4AI本体に各アプリ固有の業務ロジックを入れないことです。

RQDB4AIの責務は次のように絞ります。

  • queueを受け付ける
  • Python callableを実行する
  • queued / started / finished / failed などの状態を持つ
  • resultを保存する
  • job detail APIで状態を返す

一方で、OSS登録、AIニュース考察、技術記事要約、UStoryの小説生成などの意味は、それぞれのアプリ側job wrapperが持ちます。

Web UI
  -> RQDB4AI /api/enqueue
  -> RQ worker
  -> project側 jobs.py
  -> LLM / 保存API / SNS告知
  -> RQDB4AI result
  -> Web UI polling

この分離がとても大事です。

RQDB4AIは「このジョブは何の事業成果を作ったか」を知らなくていい。知るべきなのは、ジョブが実行され、共通resultが返ってきたことだけです。


今回のWebポーリング構成

今回の実装では、Web画面の動きを次のようにしました。

1. ユーザーが登録ボタンを押す
2. PHPがRQDB4AIへenqueueする
3. RQDB4AIがjob_idを返す
4. Web画面に「キュー登録済み / 完了待ち」を表示
5. ブラウザがPHPのjob_status APIを数秒ごとに叩く
6. PHPがRQDB4AI /api/jobs/{job_id} を問い合わせる
7. finishedになったら画面を自動リフレッシュ
8. 保存済みデータを読み直して登録内容を表示

ポイントは、ブラウザからRQDB4AIを直接叩かないことです。

ブラウザにRQDB4AIのAPI tokenを出してはいけません。そのため、各PHP画面に小さなプロキシAPIを用意しています。

oss.php?api=job_status&job_id=...
ainews.php?api=job_status&job_id=...
aitech.php?api=job_status&job_id=...
ustory.php?api=job_status&job_id=...

このPHP APIは管理者ログインを確認したうえで、サーバ側からRQDB4AIへ問い合わせます。

つまり、ブラウザに見えるのは自分のWebアプリのAPIだけです。RQDB4AIのトークンや内部URLは外に出ません。


UIとして何が良くなるか

この構成にすると、ユーザー体験がかなり自然になります。

以前は、登録ボタンを押すと「AI生成中... 1〜2分かかります」と表示して、PHPが長い処理を待っていました。

今は違います。

  • まず「キュー登録済み」と表示できる
  • job_idを画面に出せる
  • 「待機」「実行中」「完了」「失敗」を表示できる
  • 完了したら自動でリフレッシュできる
  • 失敗したらKDeck/RQDB4AIで追跡できる

ユーザーは、処理が裏側で進んでいることを理解できます。Webサーバ側も、長時間LLM処理を抱え込まずに済みます。

この「ボタンを押したらキューへ投げる。画面は状態を見るだけ」という分担は、LLMアプリではかなり強いです。


worker側は既存処理を再利用できる

今回、oss.phpustory.php は専用のjob wrapperを用意しました。

  • oss_jobs.generate_register_job
  • ustory_jobs.generate_ustory_job

一方、ainews.phpaitech.php は、もともと saveainews.php / saveaitech.php に登録ロジックがありました。

これを無理にPythonへ丸ごと移植すると、既存のHTML取得、文字コード補正、保存、SNS告知のロジックを二重管理することになります。

そこで、RQDB4AI worker側に content_register_jobs.py を追加し、workerが既存の保存APIを署名付きで呼ぶ構成にしました。

RQDB4AI worker
  -> content_register_jobs.ainews_register_job
  -> saveainews.php を署名付きPOST
  -> 既存ロジックで保存・SNS告知

この形なら、Webからは直接長時間処理を呼ばず、既存の保存ロジックも活かせます。

署名にはサーバ側の秘密情報を使い、ブラウザからの未認証呼び出しは拒否します。


result形式をそろえる

RQDB4AIで複数アプリを運用するときに大事なのは、result形式をそろえることです。

たとえば次のような形です。

{
  "ok": true,
  "status": "ok",
  "items": 1,
  "metrics": {
    "created": 1,
    "duplicate": 0,
    "remote_status": "ok"
  },
  "note": "AITech register status=ok title=...",
  "artifacts": [
    {"type": "url", "label": "source", "url": "https://example.com/article"}
  ]
}

Web画面やKDeckは、この共通resultを見れば、個別アプリの細かい事情を知らなくても状態を扱えます。

逆に、ここを曖昧にすると危険です。

  • enqueueできただけで完了扱いになる
  • workerが起動しただけで成功扱いになる
  • 実際の登録件数が0なのに成功に見える
  • 昨日のジョブが今日も「本日完了」に見える

ジョブ運用では、「キューに入った」と「業務成果が完了した」を分ける必要があります。

RQDB4AIは前者を管理し、project側job wrapperが後者をresultとして返す。この役割分担が重要です。


KurageやKDeckとの相性

この設計は、KurageやKDeckとも相性が良いです。

Kurageは、AIでショート動画やブログ解説動画を生成する仕組みです。

  • Kurage: https://kurage.exbridge.jp/
  • Kurage動画一覧: https://kurage.exbridge.jp/kuragev.php
  • KDeck: https://kurage.exbridge.jp/kdeck.php

動画生成も、LLM処理、画像生成、音声生成、動画レンダリングなど、時間がかかる処理の集合です。

そのため、Web画面で同期的に待つより、ジョブとして管理し、状態を表示し、完了後に動画ページへ誘導する方が自然です。

今回の oss.phpainews.phpaitech.phpustory.php のポーリング方式は、Kurage系の動画生成UXとも同じ考え方です。

「AIが処理中であること」を隠すのではなく、ジョブとして見えるようにする。

これが、AIエージェント時代の業務画面では重要になります。


実装パターン

実装パターンをまとめると、次のようになります。

1. Webはenqueueだけ行う

POST /page.php?api=enqueue_register
  -> RQDB4AI /api/enqueue
  -> job_idを返す

WebのPOSTでは、LLM生成や重いスクレイピングを実行しません。

2. ブラウザはjob statusをポーリングする

GET /page.php?api=job_status&job_id=...
  -> PHPがRQDB4AIへ問い合わせ
  -> label / done / failed / note を返す

ブラウザは3秒程度の間隔で状態を確認します。

3. 完了したら自動リフレッシュ

if done:
  location.href = reload_url

リフレッシュ後は、保存済みJSONやDBを読み直し、登録された内容を通常表示します。

4. 失敗時はjob_idを残す

失敗時は、ユーザーにjob_idを見せます。

KDeckやRQDB4AIの管理画面で、そのjob_idを追跡できるからです。


まとめ

LLMを使う処理は、Webリクエストの中で最後まで待つより、キューに投げる方が安定します。

特に、次のような処理はキュー化した方がよいです。

  • LLMによる記事生成
  • URL本文取得とAI要約
  • X投稿からの考察生成
  • OSSリポジトリ分析
  • 動画生成
  • YouTube投稿
  • SNS告知まで含む自動化

RQDB4AIを使うことで、これらを「見えるジョブ」として扱えます。

Web画面は軽く保ち、workerが重い処理を担当し、ブラウザはポーリングで状態を追う。完了したら自動でリフレッシュして成果物を表示する。

この構成は、AI OSSや個人開発の実験だけでなく、実運用の業務システムにもかなり使いやすい形です。

AIアプリを作るときは、LLMの呼び出しそのものだけでなく、「待たせ方」「失敗の見せ方」「完了の確認方法」まで設計する必要があります。

RQDB4AI + Webポーリングは、そのための現実的な基盤になります。

「AnthropicのFable 5とMythos 5」に何が?米政府のAI規制が描く未来 06-13

本記事はHorizonを使いAI/LLM・バイブコーディング・Web3・スタートアップのニュースを自動収集・要約したものです。 「AnthropicのFable 5とMythos 5」に何が?米政府のAI規制が描く未来 🚨 Anthropicの最先端モデルに異変?米...