Unicornのgraceful restartで少しハマった件

RailsサーバーのUnicornはmasterプロセスにUSR2シグナルを送ると、新しい設定・アプリのリロードを無停止で行うgraceful restartな動きをしてくれます。
この仕組を理解してなかったのでそれのメモ。

ドキュメントも何も見てない時の認識

  1. 現行のmasterが新しい設定・アプリをロードした新masterプロセスをforkする
  2. 新masterとそこから生えるworkerがreadyになって、新旧のUnicornが処理を行う
  3. 頃合いを見て旧masterは勝手に落ちる

と思ってたんだけど、実際試してみても3のタイミングになっても旧masterがなかなか落ちてくれない。
旧masterのCPU利用率はだんだん下がってきているので、それがなくなったら落ちるのかと勝手に思ってたけどそうでもない。ウーンとなっててドキュメント見たら普通に書いてありました。

USR2を送った時の動き

  1. 現行のmasterが新しい設定・アプリをロードした新masterプロセスをforkする (新masterでpidは更新され、旧masterのはpid.oldbinという別名になる)
  2. 新masterとそこから生えるworkerがreadyになって、新旧のUnicornが処理を行う

だけで、新しいmasterでリクエストを受けるためにはこれにプラスして

  1. 旧master (pid.oldbin なプロセス) にQUITシグナルを送って旧masterを停止

する必要があるっていうことが分かりました。もう少し詳細にだと

  1. 新旧masterが同時に上がっている時に旧masterにWINCHシグナルを送る
  2. 旧masterから生えてるworkerが全部落ちる、旧masterはそのまま
  3. 旧masterにQUITシグナルを送って停止する

というステップを踏むことも出来ます。

ハマった理由

デフォルトのunicornのconfigだと、旧masterにQUITを送るところがまるっとコメントアウトされてて気付きにくかった。。
before_fork のこの部分

   old_pid = "#{server.config[:pid]}.oldbin"
   if old_pid != server.pid
     begin  #     sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
       Process.kill(sig, File.read(old_pid).to_i)
     rescue Errno::ENOENT, Errno::ESRCH
     end
   end

これがないと、そりゃ旧master落ちないわと。"頃合いを見て"ってなんだよ俺。 @hjm_ssk から教えてもらいました。

ちなみに

新旧masterが上がっているときに、新masterだけ狙ってHUPを送るとどうなるかですが、こんなログが出て失敗します。

E, [2012-10-24T21:29:16.731271 #22072] ERROR -- : old PID:21656 running with existing pid=/path/to/app/shared/pids/unicorn.pid.oldbin, refusing rexec

なるほどなるほど。

というのをふまえて

実際には今回のハマった理由の設定がunicorn側にあれば、capistrano等でのunicorn再起動タスクで意図した通りgraceful restartが行われます。

File: SIGNALS [Unicorn: Rack HTTP server for fast clients and Unix] をよく読みましょうという話でした。