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

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

Passenger みたいに touch で Unicorn をリスタート

こんにちは、tahara です。

いこーよ では Passenger を使っていました。 最近チケット毎に Docker でステージングを作り、リリースする流れになった中、 デプロイ時の再起動でリクエストがつまってしまうのが問題になってきました。 そのため Unicorn に変えました。

実は以前 Uricorn を使っていました(さらにその前は Passenger です)。 そのころはさくらの専用サーバでした。

さくらから AWS にのりかえた際に Passenger にしました。 AWS のオートスケールでのデプロイ対応のためです。

いこーよは NFS サーバを動かしており、全 Rails サーバは NFS マウントして最新のコードを共有しています。 そうすれば、いつオートスケールしても問題なく最新のコードを参照でき、デプロイ毎に AMI を使り直す必要もありません。 オートスケールでの Rails サーバのたちあがりも、起動時にコードを最新にする必要がないので高速です。

オートスケールで Rails サーバが起動、停止するなか、 どの Rails サーバがいま動いているかを気にすることなく、デプロイは NFS サーバに対してだけおこないます。

Passenger なら全 Rails サーバの再起動は NFS サーバで touch tmp/restart.txt するだけです。

しかし Unicorn では USR2 シグナルを各 Rails サーバでなげる必要があります。

というわけでちょっとハックしました。

unicorn.conf.rb

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

  # watch restart.txt
  if worker.nr == 0
    listener = Listen.to('/app/current/tmp/unicorn_restart',
                         force_polling: true ) do |modified, added, removed|
      pid = server.config[:pid]
      if pid == server.pid
        begin
          Process.kill("USR2", File.read(pid).to_i)
        rescue Errno::ENOENT, Errno::ESRCH
        end
      end
    end
    listener.start
  end
end

上記のコードでは /app を NFS マウントしています。 デプロイ時にNFS サーバで touch /app/current/tmp/unicorn_restart/restart.txt します。 そうすると Unicorn 自身がファイル変更を検知し、自分自身に USR2 シグナルをなげます。 まるで Passenger みたい。

NFS の場合は inotify が使えません。そのため incron の使用もあきらめ、上記の Listen でも force_polling: true としています。

また NFS のマウントオプションに actimeo=3 を付けキャッシュの有効期間を 3 秒にしました。 デフォルトでは 60 秒なので、Unicorn の再起動までかなり待つはめになります。 ちなみに actimeo=0 にしたら Unicorn が起動しなくなってしまいましたorz

Passenger と同じく touch /app/curret/tmp/restart.txt にしたかったのですが、 /app/curret/tmp 以下にあるファイル数が多いとファイル監視で CPU リソースをかなり使ってしまいます。 そのため専用のディレクトリ /app/current/tmp/unicorn_restart を用意しました。

問題点は NFS サーバが単一障害点である、ことですね...

ちにみに、いこーよでは Passenger と Unicorn でほとんどパフォーマンスに差はありませんでした。