How to upgrade a big Rails application

Why should we upgrade applications?

  • Software development issues
    • Security issues
    • Dependency hell
    • New software
    • Improved APIs
    • New features
    • Performance
  • Involving new people
    • Developers
    • Novice Developers

About upgrading applications

Upgrading an application especially a big one is a complicated task, but it shouldn’t be so. It should be easy!

It’s an opportunity to learn something new.

It’s a good chance to contribute to Open Source.

Long-running branch strategy

Long-Running Branches Considered Harmful - New Relic Blog: https://blog.newrelic.com/culture/long-running-branches-considered-harmful/

Shopify’s Rails upgrade story

Shopify is one of the largest Ruby on Rails codebases in existence - Shopify Engineering Blog

RailsConf 2017: Upgrading a big application to Rails 5 by Rafael França

Link: https://www.youtube.com/watch?v=I-2Xy3RS1ns

Upgrading Shopify to Rails 5: https://engineering.shopify.com/blogs/engineering/upgrading-shopify-to-rails-5-0

Dual boot strategy

Dual boot is the process of booting your application with a different set of dependencies.

Dual boot: BUNDLE_GEMFILE

# Gemfile
gem "rails", "~> 5.2.2"
eval_gemfile "Gemfile.common"
# Gemfile.next
gem 'rails', '~> 6.0.0.beta3'
eval_gemfile "Gemfile.common"
# Gemfile.common
source "https://rubygems.org"

ruby "2.6.2"

# ...
$ bundle install # Gemfile.lock
$ BUNDLE_GEMFILE=Gemfile.next bundle install # Gemfile.next.lock
$ rails runner "p Rails.version"
"5.2.2.1"
$ BUNDLE_GEMFILE=Gemfile.next rails runner "p Rails.version"
"6.0.0.beta3"

Dual boot: BUNDLE_GEMFILE

You need to deal with three Gemfiles and the confusion that comes with it:

Gemfile, Gemfile.next, Gemfile.common vs. Gemfile

You need to ensure that all the lockfiles are in sync whenever a developer updates or adds a dependency:

$ bundle install # Gemfile.lock
$ BUNDLE_GEMFILE=Gemfile.next bundle install # Gemfile.next.lock

vs.

$ bundle install # Gemfile.lock, Gemfile.next.lock

https://github.com/Shopify/bootboot

Bootboot is a Bundler plugin meant to help dual boot your ruby application.

Installation

  1. In your Gemfile, add this
    plugin "bootboot", "~> 0.1.2"
    
  2. Run bundle install
  3. Run bundle bootboot
  4. Commit the Gemfile and the Gemfile_next.lock

Dual boot: Shopify/bootboot

diff --git a/Gemfile b/Gemfile

-gem 'rails', '~> 5.2.2'
+if ENV['DEPENDENCIES_NEXT']
+ gem 'rails', '~> 6.0.0.beta3'
+else
+ gem 'rails', '~> 5.2.2'
+end
$ DEPENDENCIES_NEXT=1 bundle update rails # Gemfile_next.lock
$ rails runner "p Rails.version"
"5.2.2.1"
$ DEPENDENCIES_NEXT=1 rails runner "p Rails.version"
"6.0.0.beta3"

Dual boot: Make it work

$ rails test
$ DEPENDENCIES_NEXT=1 rails test
if ENV['DEPENDENCIES_NEXT']
  def execute
    # ...
  end
else
  def execute
    # ...
  end
end

Dual boot: Rollout to production

$ RAILS_ENV=production rails server
$ DEPENDENCIES_NEXT=1 RAILS_ENV=production rails server

Upgrading GitHub to Rails 3 with Zero Downtime: http://shayfrendt.com/posts/upgrading-github-to-rails-3-with-zero-downtime/

Upgrading a Rails application incrementally: http://www.recursion.org/incremental-rails-upgrade/

Dual boot: Gradual rollout to production

# http://nginx.org/en/docs/http/load_balancing.html#nginx_weighted_load_balancing
upstream myapp1 {
  server srv1.example.com weight=99;
  server srv2.example.com weight=1;
}
srv1.example.com:~/myapp1$ RAILS_ENV=production rails server
srv2.example.com:~/myapp1$ DEPENDENCIES_NEXT=1 RAILS_ENV=production rails server

If you have lots of servers in production, you can just roll out the app with the next dependencies on 1% of servers and on the 99% servers run the app with the current dependencies and gradually change the ratio.

Once you are 100% in production with the app that uses the next dependencies, then it’s time to remove all if ENV['DEPENDENCIES_NEXT'] conditions from the codebase and prepare to the next upgrade.

RailsConf 2018: Upgrading Rails at Scale by Edouard Chin

Link: https://www.youtube.com/watch?v=N2B5V4ozc6k

Eliminate deprecations from your codebase: https://github.com/Shopify/deprecation_toolkit

Thanks!