Worker 自己回復(self-healing)

各 worker は「壊れたら自分で気づいて作り直す」自己回復ループを持つ。 起動時に CPU コアへレーン数を自動調整し、hub との接続が切れてもその場で再接続し続け一定ジョブ数ごとに drain して自動リサイクル、さらに hub 側が応答の途絶えた worker を自動で掃除する。 手動の再起動・登録削除は不要。

ライフサイクル図

起動 → 登録 → 実行 のメイン経路。WS が切れても RECONNECTING無期限に再接続(自滅はしない)。 終了は N ジョブ後の drain リサイクルのみで、EXIT を経て docker が作り直す循環。

① docker run / restart ② STARTING コア検出→レーン数決定 ③ REGISTER hub 登録・心拍開始 ④ RUNNING(接続中・ジョブ処理) ジョブ受信 → lane 取得(生存確認)→ 実行 → 解放 15s ごとに心拍 jobs_done++ を計数 lane の Chrome は watchdog が常時監視・自動復旧 RECONNECTING WS 断 → 無期限に再接続(自滅しない) DRAINING 新規受付停止・実行中のみ完了 EXIT 自滅 WS断 復帰 jobs≥N / 停止指示 完了後 docker restart: unless-stopped が新しいプロセスとして作り直す(state クリーン・新規登録) Hub(並行) 登録・心拍を受信し active 一覧を維持 再接続時は orphan ジョブを reconcile ⑤ reaper(定期) 心拍 STALE 超過 & 未接続 → 登録を自動削除

状態の意味

状態意味抜け方
STARTING起動。CPU コア数を見てレーン数を決め、各レーンの Chrome を立ち上げる。→ REGISTER
REGISTERhub へ WebSocket 接続し、capacity(=レーン数)を登録。心拍を開始。→ RUNNING(失敗時はバックオフで再接続を継続)
RUNNING接続中。ジョブを受けて lane で実行、15s ごとに心拍、完了数を計数。WS断→RECONNECTING / N到達→DRAINING
RECONNECTINGhub との WS が切れ、バックオフで再接続を試行。復帰→RUNNING(無期限に再接続・自滅しない
DRAINING新規ジョブの受付を止め(capacity=0)、実行中のジョブだけ完了させる。→ EXIT
EXITリサイクル(drain 完了後)にプロセスを終了。docker の restart: unless-stopped が新しいプロセスとして作り直す。→ docker → STARTING

① 同時実行の自動サイジング

起動時に worker が使える CPU コア数からレーン数(同時実行数)を自動で決める。 1 レーン ≒ Chrome 1 つ+付随処理で WORKER_LANE_CORES コアを消費する前提でコア予算を割る。

式: lanes = clamp( ⌊ WORKER_CPU_BUDGET ÷ WORKER_LANE_CORES ⌋ , 1 , WORKER_MAX_LANES )
MAX_CONCURRENT を明示した場合はそれを優先(自動算出を無効化)。

CPU 予算LANE_CORESMAX_LANES→ レーン数
2421
4421
8422(上限で頭打ち)
32422(上限で頭打ち=現状維持)

共有ホスト注意: 複数 worker が 1 物理ホストを共有する場合、nproc(見かけのコア数)ではなく その worker の公平取り分WORKER_CPU_BUDGET に設定する(例: ホスト32コアを worker 8台で割るなら 4)。 ホストに余力がある(CPU 使用率が低い)なら WORKER_MAX_LANES を上げてレーンを増やせる。

② 接続断は「その場で再接続」で耐える

hub との WS が切れると worker はバックオフ(最大15s)で無期限に再接続を試み続けるRECONNECTING)。 復帰すれば in-flight を畳んで RUNNING に戻る。プロセスは自滅しない。 残ったゴースト登録は hub 側の ④ reaper が自動で掃除するため、その場再接続でも登録は溜まらない。

「接続失敗で自滅→再起動」(Selenium の SHUTDOWN_ON_FAILURE 相当)は意図的に採用していません。 試験導入したところ、重い動画DLでイベントループが一時的に飢餓になり心拍が数十秒途絶えただけの「生きている worker」まで “接続失敗”と誤判定して落とし、しかも全台一斉デプロイと噛んで再起動ストームを起こした。 そのためその場再接続(reconnect-in-place)を既定に戻している。 将来再導入するなら、心拍遅延ではなく 「一度も登録できていない=真の接続失敗」だけを条件にすること (WORKER_RECONNECT_GIVEUP_S は将来用に予約。現在は無効)。

③ N ジョブごとに自動リサイクル

完了ジョブ数が WORKER_RECYCLE_AFTER_JOBS(既定 200)に達すると、worker は DRAINING に入る。 新規ジョブの受付を止め、実行中のジョブだけ完了させてから自滅 → 再起動する。

④ hub 側 stale worker の自動掃除(reaper)

hub は定期タスクで worker 登録を走査し、心拍が WORKER_STALE_REAP_S(既定 300 秒)以上途絶え、かつ 生きた WS 接続を持たない登録を自動で削除する。

設定一覧

Worker 側

環境変数既定説明
MAX_CONCURRENT(自動)レーン数を明示。設定すると自動サイジングを無効化。
WORKER_CPU_BUDGETnproc自動サイジングに使う「この worker の実効コア数」。共有ホストでは公平取り分を設定。
WORKER_LANE_CORES41 レーンが要するコア目安。
WORKER_MAX_LANES2自動サイジングの上限(既定は現状維持の 2。余力があれば上げる)。
WORKER_RECONNECT_GIVEUP_S—(無効)将来用に予約。現在は未使用(自滅は不採用 → 参照)。
WORKER_RECYCLE_AFTER_JOBS200完了ジョブ数がこれに達したら drain→自滅→再起動。0 で無効。

Hub 側

環境変数既定説明
WORKER_STALE_REAP_S300心拍がこの秒数途絶え、かつ未接続の登録を reaper が自動削除。
WORKER_REAP_INTERVAL_S60reaper の実行間隔。

既存の ping_interval=30 / ping_timeout=120(WS キープアライブ)、WORKER_TTL=120(active 判定)、 各レーンの Chrome watchdog はこれまで通り併用される。本ページの 4 機能はそれらの上に重なる上位の自己回復層

確認コマンド

# 起動時に決まったレーン数(worker ログ)
docker logs <worker> 2>&1 | grep -iE "lanes|MAX_CONCURRENT|cpu budget"

# 自滅→再起動が起きたか(docker の再起動回数)
docker inspect -f '{{.RestartCount}} {{.State.StartedAt}}' <worker>

# リサイクルの発生(drain ログ)
docker logs <worker> 2>&1 | grep -iE "drain|recycle|jobs_done"

# hub の reaper が掃除した記録
docker logs paprika-hub-1 2>&1 | grep -iE "reap|forgot|stale worker"

# active 一覧(ゴーストが残っていないこと)
curl -s http://<hub>:8000/workers | python -c "import sys,json;w=json.load(sys.stdin).get('workers',[]);print('active',sum(1 for x in w if (x.get('status')or x.get('state'))=='active'),'/ total',len(w))"