こんにちは tahara です。
デプロイ時の Unicorn リスタートがときどき失敗して悩んでいました。 幸い本番環境では発生せず、ステージング環境と開発環境で発生していました。
リスタートは unicorn-4.1.1/examples/init.sh の upgrade を使っています。 upgrade は sig USR2 && sleep 2 && sig 0 && oldsig QUIT とい一連の流れになっています。 調べてみると sig USR2 で新しい PID ファイルが作成されるのですが、 sig 0 の時点でまだそれができていなくて失敗していました。 本番環境はサーバの性能が高いので sleep 2 で間に合っていましたが、 ステージング環境等では間に合わなかったんですね。
そこで、次のように sig 0 が成功するまで一定期間リトライするようにしました。
upgrade) echo -n "sig USR2" if sig USR2 then sleep 1 n=$UPGRADE_TIMEOUT while ! sig 0 && test $n -ge 0 do printf '.' && sleep 1 && n=$(( $n - 1 )) done echo if test $n -lt 0 && ! sig 0 then echo >&2 "sig SUR2 failed!" exit 1 fi echo -n "oldsig QUIT" if oldsig QUIT then n=$TIMEOUT while test -s $old_pid && test $n -ge 0 do printf '.' && sleep 1 && n=$(( $n - 1 )) done echo if test $n -lt 0 && test -s $old_pid then echo >&2 "$old_pid still exists after $TIMEOUT seconds" exit 1 fi echo "ok" exit 0 fi fi echo echo >&2 "Couldn't upgrade, starting '$CMD' instead" $CMD ;;
これでうまくリスタートできるようになりました。
ついでに、何かの拍子に古い方のプロセスに QUIT を送れなかった時の対策として、 Unicorn の設定ファイルで QUIT を送るようにしました。
before_fork do |server, worker| # the following is highly recomended for Rails + "preload_app true" # as there's no need for the master process to hold a connection defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! # oldsig QUIT old_pid = "#{server.config[:pid]}.oldbin" if File.exists?(old_pid) && server.pid != old_pid begin Process.kill("QUIT", File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH end end end
これなら init.sh の方では sig USR2 だけで、後は Unicorn に古いプロセスの QUIT を まかせればいいかと思いましたが、 そうすると新しいプロセスのワーカが動き出すまで古いワーカが処理を行ってしまい、 DB のマイグレーションを行った時なんかは悲しいことになりそうです。
上記のように init.sh で古いプロセスを QUIT する場合は、 新しいプロセスのワーカが動き出すまでリクエストは待たされるので、 まだこちらの方がいいんじゃないかしらん、というところです。