Michael Trojanek (relativkreativ) — Bootstrapper and creator of things

This article was published on March 10th 2016 and takes about 6 minutes to read.

Use it with caution — it is probably still valid, but it has not been updated for over a year.

5 steps to cut your deploy time in half

Deploying your application should be as fast as possible. Only a fast deployment routine encourages you to deploy often and iterate on your applications.

The steps outlined here are direct excerpts from my book Efficient Rails DevOps. If you find this article useful, get the book — it's full of actionable advice like this.

For the steps to work I assume that you manage your application's codebase with git and use your own deploy routine (preferably using Ansible in order to unleash this article's full potential). However, they can be adjusted to also work with deploy tools like Capistrano or Mina with a little effort — the principles stay the same.

1. Skip bundle installation

Bundler provides the bundle check command which is fast and tells us if any new gems have to be installed in order to make our application work. It exits with 0 if all dependencies are satisfied so we only have to run bundle install when it evaluates to non-zero.

Time saved: Not much (but we are just warming up).

2. Skip asset precompilation

Asset precompilation can take quite some time — even if there are no new assets to precompile. Since not every change to your application's codebase involves changes to its CSS- and JavaScript-files, it pays to conditionally skip this step.

The main question is: How do we find out if we need to precompile our application's assets?

We can skip this step if the commit we want to deploy changed a file in our assets directories compared to the commit that represents the currently reployed version of our application.

To get the git SHA of the current commit, we run the command git rev-parse HEAD from our repository's root directory (before we pulled the latest changes). This will give us a git SHA like 266e7a3a6910be5e588bdb9663d5073918c4a49b which we need to remember.

Now after pulling the latest changes from a git remote, we can use the following command to check for changes in one of our application's assets directories:

git diff --name-only 266e7a3a6910be5e588bdb9663d5073918c4a49b HEAD | grep -E "(app|lib|vendor)/assets"

The git diff command lists all files that have changed between these two commits so the return code of the above command tells us whether we need to precompile our assets or not: If $? evaluates to 1 we can skip asset precompilation because no assets have changed.

Time saved: Depending on number and complexity of your assets, skipping unnecessary precompilation can save up to minutes.

3. Skip database migrations

In order to know if our database needs migration, we first have to find out what was the last migration that has been applied. The easiest way to do this is by looking it up in our application's database. Each Rails application having a database stores all applied migrations' names in a table called schema_migrations so that's where we can go looking for it.

Assuming that you run MySQL/MariaDB, running the following command from your application's root directory will give you the latest applied migration's name (do not forget to substitute USER, PASSWORD and MYAPP to match your environment):

mysql --user=USER --password=PASSWORD --batch --skip-column-names --execute="USE MYAPP; SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;"

The --batch and --skip-column-names flags make sure that we get just the migration's name (like 20150330095124). The actual command probably differs if you are using another database system.

Now that we know the last migration which has been applied to our database, we need to find the latest available migration. We can do this by running the following command from our application's root directory:

ls db/migrate | tail -1 | cut -d '_' -f 1

Rails migrations are named in the format 20150913132628_create_customers.rb. Since we only want the timestamp of the latest migration, this command lists all files in our application's db/migrate directory (ls db/migrate), reduces the output to just the last file (tail -1), splits its filename at every _ and displays the first item of the resulting array.

Now we have everything together to judge whether our application's database needs to be migrated or not. We just compare the two values — if they differ, then we need to migrate. If they do not, we can safely skip this step.

You may have noticed that there is a rake task called db:migrate:status which lists our application's migrations along with their status. Unfortunately, it always exits with 0 which does not make it suitable for using it inside a script — that's why I think the outlined method is much more reliable than parsing the rake command's output.

Time saved: 1.25 seconds on average.

4. Disable Ansible's facts gathering

If you are using Ansible to deploy your Rails application but you do not make use of facts, disable fact gathering by setting gather_facts: no in your deploy playbook.

Time saved: About 3 seconds.

5. Enable Ansible's pipelining

Enabling pipelining reduces the number of SSH operations required to execute an Ansible module on a remote server by executing many modules without actual file transfer.

To enable pipelining, you can either create a configuration file for your user (~/.ansible.cfg) or edit Ansible's global configuration by setting pipelining in the ssh_connection section:

[ssh_connection]
pipelining=True

Alternatively you can temporarily enable pipelining by running export ANSIBLE_SSH_PIPELINING=1 before you run your deploy playbook (you can turn it back off if you need to with export ANSIBLE_SSH_PIPELINING=0).

This is actually the better way because in order for pipelining to work, you have to disable requiretty in your target machine's sudoers configuration. It is probably better to enable pipelining on demand if you manage machines where disabling requiretty is not possible.

Time saved: Where pipelining is possible, it results in a really big performance improvement — bringing the time needed to deploy this website from 23 seconds down to 11.

Conclusion

If I were to apply only one of the steps outlined here, I'd go with conditionally skipping asset precompilation — it saves you more time everytime your application's assets get more complex.

However, it definitely pays to squeeze every bit of speed out of your deploy routine. Being able to deploy new releases quickly makes sure that deploying does not get postponed and fixes and new features for your customers are available as soon as possible. Stay agile!

Expand your DevOps skills!

Join hundreds of Rails developers and operators on my email list and get my ebook Build Your Own Rails Server as a free welcome gift.

No spam — guaranteed. You can leave at any time.