July 14, 2012

Capistrano is dead - use Mina

Up until lately I was happy using Capistrano for my deployments. But then I noticed the following:

Capistrano was utilizing 70 % of a physical CPU core when deploying on the go.

I’m on a dual core system so this impacts my Mac Book Pros overall performance, battery life and it kept me waiting for a long long time. My iPhone was at edge speed so I figured Capistrano spent most of the time waiting for network I\O - busy waiting that is.

So I profiled Capistrano using perftools.rb. Capistrano spent about 50% on Compat.io_select over each deployment run. That’s 50 % of a total of 40 seconds it took to deploy & restart my application server. The percentage spent waiting on Compact.io_select decreases to about 10% as the network speed increases to regular DSL levels.

The interesting part is that this is just CPU time. When profiling wall time Compact.io_select is at 9%, while Object#process_iteration is at 84%. Object#process_iteration is a wrapper Capistrano introduces around Kernel#select which ‘should’ not do busy waiting. But Capistrano kept my CPU busy anyways.

I converted my Capistrano configuration to Mina to see if it was any better.

Contrary to Capistrano Mina creates a bash script based on your deployment configuration and executes the script on the server side using a single SSH connection.

In theory this should be way faster than Capistrano which creates multiple SSH connections. It should have a far smaller CPU footprint as well given that Ruby is only used to assemble a bash script.

Timing both setups led to the following results:

$ time mina deploy
real  0m23.999s
user  0m0.188s
sys  0m0.054s
$ time cap deploy
real  0m40.561s
user  0m0.897s
sys  0m0.191s

As you can see Mina deployments take about half the time it takes Capistrano to deploy.

For comparison only, here’s a stripped-down version of my Capistrano configuration

require "rvm/capistrano"
require 'bundler/capistrano'
set :application, "my-app"

namespace :deploy do
  task :start, :roles => :app, :except => { :no_release => true } do
    run "sudo supervisorctl start #{application}:*"
  end

  task :stop, :roles => :app, :except => { :no_release => true } do
    run "sudo supervisorctl stop #{application}:*"
  end

  task :restart, :roles => :app, :except => { :no_release => true } do
    run "sudo supervisorctl restart #{application}:*"
  end
end

as well as Minas:

require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rvm'

task :environment do
  invoke :'rvm:use[ruby-1.9.3-p125@default]'
end

desc "Deploys the current version to the server."
task :deploy => :environment do
  deploy do
    invoke :'git:clone'
    invoke :'bundle:install'

    to :launch do
      queue 'sudo supervisorctl restart my-app:*'
    end
  end
end

Conclusion

Mina is faster than capistrano, uses less CPU and less network bandwidth. Since it generates a bash script for deployment with appropriate exit statements it’s also much more transparent and easier to debug than capistrano will ever be.

Verdict:

Use Mina already!

© Raphael Randschau 2010 - 2022 | Impressum