Getting Started with Unicorn

I’ve been doing rolling deploys with a Mongrel cluster for over half a year now, and it works. Most of the time. Unless a Mongrel takes too long to shut down or something. Eh.

But then we all saw GitHub’s switch to Unicorn. I thought it sounded very cool, but maybe too much work to bother switching.

Oops.

Have a couple minutes? Good. Here you go …

Getting Started

Hit up the gem repository and gem install unicorn. It needs to compile, but that’s no big deal.

Now, shut down your script/server. Just go ahead and close that. Ready? Okay, now run unicorn_rails -p 3000.

Done. Good stuff. Was it good for you too?

Is It Different in Production?

Well sure. Let’s play around a little bit. It’s super simple to run Unicorn with a bunch of workers and send it commands just like you would in production.

First, create a basic config/unicorn.rb.

worker_processes 2
preload_app true
timeout 30
listen 3000

after_fork do |server, worker|
  ActiveRecord::Base.establish_connection
end

Want to know what all of that does? Check the docs, or the official example, or the Github sample. Go ahead, I’ll be here when you get back.

Now start your Unicorn server, except this time in the background and with this config file:

$ unicorn_rails -c config/unicorn.rb -D

$ pgrep -lf unicorn_rails
12113 unicorn_rails master -c config/unicorn.rb -D
12118 unicorn_rails worker[0] -c config/unicorn.rb -D
12119 unicorn_rails worker[1] -c config/unicorn.rb -D

$ cat tmp/pids/unicorn.pid
12113

Next, we’re going to send it some commands. You do this by sending signals. You’ve used the kill command? Yeah, well, it doesn’t always kill things. Watch this:

$ kill -s TTOU 12113
$ pgrep -lf unicorn_rails
12113 unicorn_rails master -c config/unicorn.rb -D
12118 unicorn_rails worker[0] -c config/unicorn.rb -D

We just dynamically removed a worker from the pool. Neat, huh? Let’s add it back, plus one:

$ kill -s TTIN 12113
$ kill -s TTIN 12113
$ pgrep -lf unicorn_rails
12113 unicorn_rails master -c config/unicorn.rb -D
12118 unicorn_rails worker[0] -c config/unicorn.rb -D
12136 unicorn_rails worker[1] -c config/unicorn.rb -D
12137 unicorn_rails worker[2] -c config/unicorn.rb -D

Where’s My Seamless Deploy!

Nice. But I know what you’re really here for. You want to know what it’s like to hot-swap Unicorn for seamless deploys? First, add this to your config/unicorn.rb:

before_fork do |server, worker|
  old_pid = RAILS_ROOT + '/tmp/pids/unicorn.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
      # someone else did our job for us
    end
  end
end

And for the win. Notice that I pgrep`d fast enough and caught it in transition.

$ kill -s USR2 12113

$ pgrep -lf unicorn_rails
12113 unicorn_rails master (old) -c config/unicorn.rb -D
12118 unicorn_rails worker[0] -c config/unicorn.rb -D
12136 unicorn_rails worker[1] -c config/unicorn.rb -D
12137 unicorn_rails worker[2] -c config/unicorn.rb -D
12239 /usr/bin/ruby1.8 /usr/bin/unicorn_rails -c config/unicorn-dev.rb -D

$ pgrep -lf unicorn_rails
12239 unicorn_rails master -c config/unicorn-dev.rb -D
12245 unicorn_rails worker[0] -c config/unicorn-dev.rb -D
12246 unicorn_rails worker[1] -c config/unicorn-dev.rb -D

Done. Good stuff. Was it good for you, too?