Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Speed up assets:precompile with Rails 3.1/3.2 Capistrano deployment

My deployments are slow, they take at least 3 minutes. The slow Capistrano task during deploy is assets:precompile. This takes probably 99% of the total deploy time. How can I speed this up? Should I precompile my assets on my local machine and add them to my git repo?

Edit: Adding config.assets.initialize_on_precompile = false to my application.rb file dropped the precompile time with half a minute, but it is still slow.

like image 704
Godisemo Avatar asked Jan 26 '12 09:01

Godisemo


People also ask

What does Rails assets Precompile do?

The Rails asset pipeline provides an assets:precompile rake task to allow assets to be compiled and cached up front rather than compiled every time the app boots. There are two ways you can use the asset pipeline on Heroku. Compiling assets locally.


2 Answers

The idea is that if you don't change your assets you don't need to recompile them each time:

This is the solution that Ben Curtis propose for a deployment with git:

 namespace :deploy do       namespace :assets do         task :precompile, :roles => :web, :except => { :no_release => true } do           from = source.next_revision(current_revision)           if releases.length <= 1 || capture("cd #{latest_release} && #{source.local.log(from)} vendor/assets/ app/assets/ | wc -l").to_i > 0             run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile}           else             logger.info "Skipping asset pre-compilation because there were no asset changes"           end       end     end   end 

Here is another approach based on asset age (https://gist.github.com/2784462) :

set :max_asset_age, 2 ## Set asset age in minutes to test modified date against.  after "deploy:finalize_update", "deploy:assets:determine_modified_assets", "deploy:assets:conditionally_precompile"  namespace :deploy do   namespace :assets do      desc "Figure out modified assets."     task :determine_modified_assets, :roles => assets_role, :except => { :no_release => true } do       set :updated_assets, capture("find #{latest_release}/app/assets -type d -name .git -prune -o -mmin -#{max_asset_age} -type f -print", :except => { :no_release => true }).split     end      desc "Remove callback for asset precompiling unless assets were updated in most recent git commit."     task :conditionally_precompile, :roles => assets_role, :except => { :no_release => true } do       if(updated_assets.empty?)         callback = callbacks[:after].find{|c| c.source == "deploy:assets:precompile" }         callbacks[:after].delete(callback)         logger.info("Skipping asset precompiling, no updated assets.")       else         logger.info("#{updated_assets.length} updated assets. Will precompile.")       end     end    end end 

If you prefer to precompile your assets locally you can use this task:

namespace :deploy do   namespace :assets do     desc 'Run the precompile task locally and rsync with shared'     task :precompile, :roles => :web, :except => { :no_release => true } do       from = source.next_revision(current_revision)       if releases.length <= 1 || capture("cd #{latest_release} && #{source.local.log(from)} vendor/assets/ app/assets/ | wc -l").to_i > 0         %x{bundle exec rake assets:precompile}         %x{rsync --recursive --times --rsh=ssh --compress --human-readable --progress public/assets #{user}@#{host}:#{shared_path}}         %x{bundle exec rake assets:clean}       else         logger.info 'Skipping asset pre-compilation because there were no asset changes'       end     end   end end  

Another interesting approach can be that of using a git hook. For example you can add this code to .git/hooks/pre-commit which checks if there are any differences in the assets files and eventually precompiles them and add them to the current commit.

#!/bin/bash  # source rvm and .rvmrc if present [ -s "$HOME/.rvm/scripts/rvm" ] && . "$HOME/.rvm/scripts/rvm" [ -s "$PWD/.rvmrc" ] && . "$PWD/.rvmrc"  # precompile assets if any have been updated if git diff-index --name-only HEAD | egrep '^app/assets' >/dev/null ; then   echo 'Precompiling assets...'   rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets   git add public/assets/* fi 

If you decide to use this approach you would probably need to change your config/environments/development.rb adding:

config.assets.prefix = '/assets_dev' 

So that while in development you won't serve the precompiled assets.

like image 147
tommasop Avatar answered Sep 23 '22 23:09

tommasop


I've just written a gem to solve this problem inside Rails, called turbo-sprockets-rails3. It speeds up your assets:precompile by only recompiling changed files, and only compiling once to generate all assets. It works out of the box for Capistrano, since your assets directory is shared between releases.

This is much more bulletproof than the solutions that use git log, since my patch analyzes the sources of your assets, even if they come from a gem. For example, if you update jquery-rails, a change will be detected for application.js, and only application.js will be recompiled.

Note that I'm also trying to get this patch merged into Rails 4.0.0, and possibly Rails 3.2.9 (see https://github.com/rails/sprockets-rails/pull/21). But for now, it would be awesome if you could help me test out the turbo-sprockets-rails3 gem, and let me know if you have any problems.

like image 43
ndbroadbent Avatar answered Sep 22 '22 23:09

ndbroadbent