サイトのリロード時に約40秒のダウンタイムが発生しています。これは私の技術的な能力を超えているため、Claudeに助けを求めています。
Claudeによると、問題はユニコーン(Unicorn)ランチャーが古いマスタープロセスに誤ったシグナル(SIGQUITの代わりにSIGTERM)を送信していることです。これにより、新しいマスターがサービスを開始する前に古いマスターが強制終了してしまいます。
修正にはわずか6行の更新が必要とのことです。しかし、まずはここで健全性チェックを行いたいです。
Claudeが生成したレポートを見るにはクリック
概要
pitchforkコードパスにおいて、config/unicorn_launcherのon_reload_pitchfork()関数は、新しいマスターがリクエストを処理できることを確認することなく、ワーカープロセスが存在し次第、古いマスターを退任させます。また、退任にはkill(SIGTERM、即時シャットダウン)が使用されており、kill -s QUIT(優雅なシャットダウン)ではありません。
その直上のユニコーンパスであるon_reload_unicorn()は、この両方を正しく処理しています。つまり、新しいマスターをウォームアップするためにcurl $LOCAL_WEBを発行し、その後QUITを送信します。
単一のsv hup unicornコマンド実行後の観測結果は、新しい世代がBooted Railsをログに記録するまで、約40秒間リクエストが完了しないというものでした。
環境
- Discourse
2026.8.0-latest.1、Rails 8.0.5.1、Ruby 3.4.10 - pitchfork 0.18.2、
RUN_PITCHFORK未設定(そのためpitchforkブランチが有効) - スタンドアロンコンテナ、nginx →
http://127.0.0.1:3000 - 2 vCPU / 4 GB ドロップレット; アプリケーションの起動に約40秒かかる
コード
config/unicorn_launcher:
function on_reload_unicorn() {
...
while [ ... workers not up ... ]; do sleep 1; done
curl $LOCAL_WEB &>/dev/null # 新しいマスターをウォームアップ
kill -s QUIT $UNICORN_PID # 優雅な終了
}
function on_reload_pitchfork() {
log "Reloading pitchfork ($UNICORN_PID)"
OLD_PID=$UNICORN_PID
pitchfork "${PITCHFORK_ARGS[@]}" &
UNICORN_PID=$!
count=0
while [ "$count" -lt 180 -a -z "$(pgrep -f -P $UNICORN_PID worker)" ]; do
log "Waiting for new pitchfork workers under $UNICORN_PID to start up..."
count=$((count + 1))
sleep 1
done
kill $OLD_PID 2>/dev/null # SIGTERM、ウォームアップなし
}
pgrep -f -P $UNICORN_PID workerは、ワーカープロセスが生成された時点で返ります。pitchforkでは、アプリケーションの読み込みが完了するずっと前にワーカーが存在するため、ループは早期に終了し、新しいマスターがまだ起動中の間に古いマスターが解体されます。
観測された挙動
production.log、1回のリロード(タイムスタンプはUTC):
23:16:49 Completed 200 OK in 95ms <- 古い世代、ウォーム
(44秒間、Completed行なし)
23:17:33 Booted Rails 8.0.5.1 application in production environment
23:17:36 Completed 200 OK in 4128ms <- 新しい世代での最初のレンダリング、コールド
23:17:39 Completed 200 OK in 200ms
その間、nginx経由で約2秒ごとにGET /をポーリングするモニターは以下を記録しました:
23:17:11 499 19.697s (クライアントが放棄; upstreamは応答せず)
23:17:29 503 16.301s
23:17:31 502 1.448s
502エラーに関するnginxのエラーログ:
2026/08/14 23:17:31 [error] 70#70: *110675 recv() failed (104: Connection reset by peer)
while reading response header from upstream, upstream: "http://127.0.0.1:3000/"
失敗したすべての行でupstream_response_timeはrequest_timeと等しいため、nginxがボトルネックではありません。リクエストは応答できないプロセスに到達しています。23:17:31でのリセットは、接続がまだキューイングされたままの古いマスターをSIGTERMが解体したことに一致します。
これは再現可能です。1回のデプロイでの2回のリロードは、それぞれBooted Rails行で終了する、同じような約40秒のウィンドウを生成しました。
リソース圧力ではありません。dmesgにOOMキルはなく、スワップ使用量は2048MB中141MB、ロード平均は0.07、両方の起動は正常に完了しました。
提案される修正
ユニコーンブランチと同様に、新しいマスターが実際にサービスを提供するまで待ってから、古いマスターを優雅に退任させる:
count=0
while [ "$count" -lt 180 -a -z "$(pgrep -f -P $UNICORN_PID worker)" ]; do
log "Waiting for new pitchfork workers under $UNICORN_PID to start up..."
count=$((count + 1))
sleep 1
done
- kill $OLD_PID 2>/dev/null
+ count=0
+ until curl -sf -o /dev/null "$LOCAL_WEB" || [ "$count" -ge 180 ]; do
+ log "Waiting for the new pitchfork master to serve a request..."
+ count=$((count + 1))
+ sleep 1
+ done
+
+ kill -s QUIT $OLD_PID 2>/dev/null
TERMの代わりにQUITを使用する半分は明確です。pitchforkはユニコーンのシグナルセマンティクスを継承しており、QUITは排水(drain)、TERMは即時停止です。
PS: これは私がAIの支援を受けて作成した初めてのレポートです。何か礼儀違反があれば教えてください。![]()