Worker 自己回復(self-healing)
各 worker は「壊れたら自分で気づいて作り直す」自己回復ループを持つ。 起動時に CPU コアへレーン数を自動調整し、hub との接続が切れてもその場で再接続し続け、 一定ジョブ数ごとに drain して自動リサイクル、さらに hub 側が応答の途絶えた worker を自動で掃除する。 手動の再起動・登録削除は不要。
ライフサイクル図
起動 → 登録 → 実行 のメイン経路。WS が切れても RECONNECTING で無期限に再接続(自滅はしない)。
終了は N ジョブ後の drain リサイクルのみで、EXIT を経て docker が作り直す循環。
状態の意味
| 状態 | 意味 | 抜け方 |
|---|---|---|
STARTING | 起動。CPU コア数を見てレーン数を決め、各レーンの Chrome を立ち上げる。 | → REGISTER |
REGISTER | hub へ WebSocket 接続し、capacity(=レーン数)を登録。心拍を開始。 | → RUNNING(失敗時はバックオフで再接続を継続) |
RUNNING | 接続中。ジョブを受けて lane で実行、15s ごとに心拍、完了数を計数。 | WS断→RECONNECTING / N到達→DRAINING |
RECONNECTING | hub との 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_CORES | MAX_LANES | → レーン数 |
|---|---|---|---|
| 2 | 4 | 2 | 1 |
| 4 | 4 | 2 | 1 |
| 8 | 4 | 2 | 2(上限で頭打ち) |
| 32 | 4 | 2 | 2(上限で頭打ち=現状維持) |
共有ホスト注意: 複数 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 に入る。
新規ジョブの受付を止め、実行中のジョブだけ完了させてから自滅 → 再起動する。
- 長時間稼働で溜まるリソース(プロセス残骸・メモリ・一時ファイル)を定期的にリセット。
- drain なので実行中のジョブは中断されない(完了を待ってから落ちる)。
0を指定すると無効化(リサイクルしない)。
④ hub 側 stale worker の自動掃除(reaper)
hub は定期タスクで worker 登録を走査し、心拍が WORKER_STALE_REAP_S(既定 300 秒)以上途絶え、かつ
生きた WS 接続を持たない登録を自動で削除する。
- 再起動などで残った古い登録(ゴースト)が放置されず自動で消える。手動削除は不要。
- 接続中の worker は対象外(生きた WS があれば消さない)。
- 管理画面の worker 一覧が実体と一致し続ける。
設定一覧
Worker 側
| 環境変数 | 既定 | 説明 |
|---|---|---|
MAX_CONCURRENT | (自動) | レーン数を明示。設定すると自動サイジングを無効化。 |
WORKER_CPU_BUDGET | nproc | 自動サイジングに使う「この worker の実効コア数」。共有ホストでは公平取り分を設定。 |
WORKER_LANE_CORES | 4 | 1 レーンが要するコア目安。 |
WORKER_MAX_LANES | 2 | 自動サイジングの上限(既定は現状維持の 2。余力があれば上げる)。 |
WORKER_RECONNECT_GIVEUP_S | —(無効) | 将来用に予約。現在は未使用(自滅は不採用 → ② 参照)。 |
WORKER_RECYCLE_AFTER_JOBS | 200 | 完了ジョブ数がこれに達したら drain→自滅→再起動。0 で無効。 |
Hub 側
| 環境変数 | 既定 | 説明 |
|---|---|---|
WORKER_STALE_REAP_S | 300 | 心拍がこの秒数途絶え、かつ未接続の登録を reaper が自動削除。 |
WORKER_REAP_INTERVAL_S | 60 | reaper の実行間隔。 |
既存の 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))"