Continuous Deployment Architecture For Ruby on Rails

Posted in: Technical Track

We have seen agile development become more popular in recent years thanks in part to the evolution of continuous integration environments like Ruby on Rails.  These development frameworks leverage quick testing and the ability to easily deploy over clusters. As deployments happen more often, we look for ways to minimize the disruption to users. Cluster architecture already contains the components we need, with multiple servers for each role, allowing us to update a subset of the system while the rest serve the business.

We recently worked with the DevOps team at one of our clients to develop an architecture that allows them to run rolling changes through the cluster using the Ruby on Rails framework. There are two principal components of change: application code on the application servers and database structure on the database servers. The application servers can easily be targeted specifically through deploy.rb. The database side of things, however, is a bit more complicated.

In order to have zero downtime, half the application servers are taken offline and updated, then the pair of master/slave standby databases have the DDLs applied to them. Traffic is then switched to these servers.

HAProxy is put in between the application and the MySQL databaseservers to act as a router for database traffic (see for specifics), providing the ability to flip the active and standby roles of the two server pairs (as well as providing a High Availability solution). Since there are slave relationships, we needed to be able to pause replication through the deployment application (Capistrano).  We were able to accomplish this by adding a file, deployrake.util, to the Rake subsystem under lib/tasks:

namespace :deployutil do

desc ‘Checks the replication status of the primary database’

task :replication_test => :environment do

# find the state of replication

mysql_res = ActiveRecord::Base.connection.execute(“SHOW SLAVE STATUS”)

mysql_res.each_hash do |row|

if row[‘Slave_IO_Running’] == “Yes” and row[‘Slave_SQL_Running’]

== “Yes” and row[‘Seconds_Behind_Master’].to_s == “0”

puts “ReplicationGood”

elsif row[‘Seconds_Behind_Master’].blank?

puts “ReplicationBroken”


puts “ReplicationBehind_” + row[‘Seconds_Behind_Master’].to_s





task :start_slave => :environment do

ActiveRecord::Base.connection.execute(“START SLAVE”)


task :stop_slave => :environment do

ActiveRecord::Base.connection.execute(“STOP SLAVE”)



We can then create tasks in deploy.rb to call these

desc “shows the current passive slave replication status”

task :get_slave_replication_status, :roles => :cron do

dbrails_env = fetch(:rails_env) + ‘_passiveslave’

# find the state of replication

set :slave_replication_status, capture(“cd #{latest_release} ;

RAILS_ENV=#{dbrails_env} rake deployutil:replication_test”).chomp



desc “stop passive slave replication”

task :stop_passiveslave_repl, :roles => :cron do

dbrails_env = fetch(:rails_env) + ‘_passiveslave’

run “cd #{latest_release} ; RAILS_ENV=#{dbrails_env} rake




We also want to be able to limit changes to specific databases so that the changes won’t go into the bin logs and propagate when the slaves are turned back on.  See for details on how to do this through an extension of ActiveRecord.  A word of caution here: setting sql_log_bin=0 to skip logging these changes will invalidate using the binlogs for point in time recovery.

You will need a full backup after the change.

Want to talk with an expert? Schedule a call with our team to get the conversation started.

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *