On some of the servers I work with, due to cheap hard drives in software RAID configuration, I’ve found that bundle install
can be extremely slow (take half an hour to complete). This obviously became unacceptable during deploys.
I thought that it might have something to do with how bundler writes a lot of small files during the installation of the gems. So I decided to try putting the deploy bundle directory (where all the gems are being installed) onto the in-memory filesystem. On Ubuntu this is /dev/shm
.
It works flawlessly. The install time improved from half an hour down to a few seconds. After the bundle install is complete however, we do not want to leave the installed gems in the memory, as during restart they would be purged. So we just copy the directory back to the disk. Strangely enough, copying the whole directory from /dev/shm
does not trash the disk so much and it only takes up to a minute for a few hundred MB of gems.
It’s cool to be able to find and utilize such a useful and simple part of Linux to solve and work around a slow hardware problem, while for everything else the server does, it’s still perfectly usable and more than capable of performing it.
Here’s my Capistrano 3 lib I use in my deploys that integrates this speedup:
namespace :bundler_speedup do
task :symlink_to_shm do
on roles(:all) do
bundle_shm_path = fetch(:bundle_shm_path)
# Make sure bundle dir exists
execute "if [ ! -d #{shared_path}/bundle ]; then mkdir #{shared_path}/bundle; fi"
# todo: what if #{shared_path}/bundle is a symlink - meaning an interrupted install from previous time?
cmds = []
# Copy the bundle dir to /dev/shm/
cmds << "cp -r #{shared_path}/bundle #{bundle_shm_path}"
# Remove the shared bundle dir and symlink the shm dir instead
cmds << "mv #{shared_path}/bundle #{shared_path}/bundle.old"
cmds << "ln -s #{bundle_shm_path} #{shared_path}/bundle"
# We're ready to do a fast in-memory bundle install now...
execute cmds.join(' && ')
info "shared/bundle was copied to /dev/shm for in-memory bundle install"
end
end
task :remove_from_shm do
on roles(:all) do
bundle_shm_path = fetch(:bundle_shm_path)
cmds = []
# Copy the shm bundle to shared
cmds << "cp -r #{bundle_shm_path} #{shared_path}/bundle.new"
# Remove the symlink and move in the dir on disk
cmds << "rm #{shared_path}/bundle"
cmds << "mv #{shared_path}/bundle.new #{shared_path}/bundle"
# Remove the in memory bundle
cmds << "rm -rf #{bundle_shm_path}"
cmds << "rm -rf #{shared_path}/bundle.old"
# Bundle is persisted and in place
execute cmds.join(' && ')
info "shared/bundle was restored from bundle install within /dev/shm"
end
end
before 'bundler:install', 'bundler_speedup:symlink_to_shm'
after 'bundler:install', 'bundler_speedup:remove_from_shm'
end
namespace :load do
task :defaults do
set :bundle_shm_path, -> { "/dev/shm/#{fetch(:application).gsub(' ', '_').downcase}_bundle" }
end
end
In a Rails project, place it in lib/capistrano/tasks/bundler_speedup.rake
. Capistrano should auto-load this for you.
This code is released under the MIT license.