アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

デプロイ時の Unicorn リスタートが失敗する件

こんにちは 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 する場合は、 新しいプロセスのワーカが動き出すまでリクエストは待たされるので、 まだこちらの方がいいんじゃないかしらん、というところです。