OpenWebUIから返事が来なくなった ― Reaperが見逃すゴーストWorkerパターンの発見と対処

はじめに

「OpenWebUIから質問を投げたのに、いつまでも⏳処理中…のまま返ってこない」

今回はそんな症状から始まった障害対応の記録です。原因を追いかけていくと、自作Reaperに潜んでいた盲点が見つかりました。新しいWorkerを追加した直後にプロセスがクラッシュすると、タスクが永久に詰まってしまうケースがあったのです。


症状:処理中のまま帰ってこない

OpenWebUIのルーティング情報には、こう表示されていました。

ドメイン: general
モデル: gemma3:1b
Worker: esxi
キュー: tasks:cpu:esxi
⏳ 処理中...

esxi というWorkerに振られているが、いつまでも応答がない。まずRedisの状態を確認しました。

redis-cli KEYS "worker:*"
redis-cli HGETALL "worker:esxi"

結果、worker:esxiはRedisに存在し、registered_at: 2026-06-07T02:25:47Zで今日の早朝に登録されていました。しかしCoordinatorのログを見ると、登録直後からesxiへのアクセスが一切ありません。

02:25:47 [register] worker_id=esxi models=['llama3.2:1b', 'gemma3:1b'] supports=['cpu_inference']
02:25:48 GET /task/cb305e3a ... 200 OK
02:25:50 GET /task/cb305e3a ... 200 OK
(以降、esxiからのアクセスなし)

esxiのWorkerプロセスは、登録直後にクラッシュしていたのです。


なぜReaperが動かなかったのか

このシステムにはv6からReaper(自己修復スレッド)が実装されています。Worker死亡やゾンビタスクを15秒間隔で検出し、別Workerへ自動再投入する仕組みです。なのに今回はなぜ動かなかったのか。

Reaperのtrigger判定ロジックを確認しました。

worker_alive = bool(r.exists(f"worker:{worker_id}"))

dead_trigger   = (not worker_alive) and age > REAPER_DEAD_GRACE_SEC
zombie_trigger = worker_alive and status == "running" and age > REAPER_RUNNING_TIMEOUT_SEC

問題が見えてきました。

  • dead_triggerworker_alive = Falseのときに発火。しかしworker:esxiはRedisに残っていたため、worker_alive = True→発火しない
  • zombie_triggerworker_alive = Trueかつstatus = "running"のときに発火。しかしWorkerがBRPOPする前にクラッシュしたため、タスクはCASクレームされずstatus = "pending"のまま→発火しない

「Worker登録済み(Redisにworker:esxiが存在)+ タスクはpending」という組み合わせが、完全にReaperの死角だったのです。

Worker登録 → プロセスクラッシュ
     ↓
worker:esxi はRedisに残る(dead_trigger: 発火せず)
     ↓
タスクはpendingのまま(zombie_trigger: runningでないので発火せず)
     ↓
永久に処理中...
Reaperが見逃すパターン

なぜesxiのWorkerはクラッシュしたのか

esxiのマシンを確認しました。

ssh hogehoge@192.168.0.41 "timeout 10 /home/hogehoge/venv/bin/python /home/hogehoge/worker.py 2>&1"
2026-06-07 02:41:03 [worker] INFO Worker starting. ID=esxi ...
2026-06-07 02:41:03 [worker] INFO Ollama OK
2026-06-07 02:41:03 [worker] INFO [sanity] checking llama3.2:1b ... OK
2026-06-07 02:41:05 [worker] INFO [sanity] checking gemma3:1b ... OK
2026-06-07 02:41:06 [worker] INFO [register] worker_id=esxi ...
2026-06-07 02:41:06 [worker] INFO Waiting on queues ['tasks:cpu:esxi', 'tasks:cpu:shared'] ...
EXIT:0

Workerプロセス自体は正常でした。timeout 10で10秒後に強制終了したためEXIT:0になっているだけで、クラッシュではありません。

原因は別にありました。systemdサービスとして登録されていなかったのです。手動で起動したプロセスが何らかの理由(SSHセッション切断等)で終了し、再起動されなかったものでした。


対処①:詰まったタスクの復旧

まずworker:esxiをRedisから削除して、Reaperに再投入させました。

redis-cli DEL worker:esxi

するとReaperがdead_triggerを発火させ、gemma3:1bを処理できる代替Workerを探しました。しかし——

[reaper] FAIL task=cb305e3a: no alternative worker can serve model 'gemma3:1b'
(reason=dead_worker); single point of failure

gemma3:1bを担当できる他のWorkerがいませんでした。ai-coreのOllamaにはgemma3:1bが入っていなかったのです。これはSPOF(単一障害点)でもありました。

対処②:ai-coreにgemma3:1bを追加してSPOFを解消

ollama pull gemma3:1b

worker_aicore.pysanity_modelsリストにgemma3:1bを追加してWorkerを再起動。

"sanity_models": [
    "hf.co/LiquidAI/LFM2.5-1.2B-JP-GGUF:Q4_K_M",
    "llama3.2:1b",
    "llama3.2:3b",
    "gemma3:1b",   # 追加
],

これでesxiが落ちてもai-coreがフェイルオーバー先になります。

対処③:esxiのsystemdサービス作成

[Unit]
Description=Coordinator Worker (esxi)
After=network.target

[Service]
User=hogehoge
WorkingDirectory=/home/hogehoge
ExecStart=/home/hogehoge/venv/bin/python /home/hogehoge/worker.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

systemctl enable --now coordinator-workerで有効化。再起動後も自動起動するようになりました。


根本修正:ghost_triggerをReaperに追加

今回の問題の本質は「Worker登録済み + pending + BRPOPしていない」という状態をReaperが認識できなかったことです。ghost_triggerという3番目のトリガーを追加しました。

worker_alive = bool(r.exists(f"worker:{worker_id}"))

dead_trigger   = (not worker_alive) and age > REAPER_DEAD_GRACE_SEC
ghost_trigger  = worker_alive and status == "pending" and age > REAPER_DEAD_GRACE_SEC  # 追加
zombie_trigger = worker_alive and status == "running" and age > REAPER_RUNNING_TIMEOUT_SEC

if not (dead_trigger or zombie_trigger or ghost_trigger):
    return
reason = "dead_worker" if dead_trigger else ("ghost_pending" if ghost_trigger else "zombie_running")

ghost_triggerの発火条件:

  • Workerはまだ登録済み(worker_alive = True
  • タスクのstatusがpending(BRPOPされていない)
  • 一定時間(REAPER_DEAD_GRACE_SEC = 15秒)経過

これにより「登録したが動いていないゴーストWorker」に積まれたタスクを検出し、代替Workerへ再投入できるようになりました。


修正後の動作確認

coordinatorを再起動後、OpenWebUIから質問を投げると正常に返答が返ってきました。

ドメイン: general / モデル: gemma3:1b / worker: ai-core

esxiのWorkerもsystemd管理下で安定稼働しています。


まとめ:3種類のReaper発火条件

トリガー 条件 想定ケース
dead_trigger worker_alive=False + age超過 Worker死亡(Redisから消えた)
ghost_trigger(新規) worker_alive=True + pending + age超過 Worker登録後クラッシュ・BRPOPせず
zombie_trigger worker_alive=True + running + 長時間超過 Worker生存だがrunningのまま固まった

今回の障害は「新Worker追加という日常的な作業」が引き金でした。セットアップ中の不完全な状態でもシステムが堅牢に動くよう、Reaperの網の目を細かくしていくことが自己修復基盤の成熟につながると感じた一件でした。