加速资产:预编译Rails 3.1 / 3.2 Capistrano部署

我的部署速度很慢,至less需要3分钟。 部署期间的Capistrano任务缓慢是资产:预编译。 这大概占总部署时间的99%。 我怎样才能加快速度呢? 我应该在我的本地机器上预编译我的资产,并将它们添加到我的git回购?

编辑:将config.assets.initialize_on_precompile = false添加到我的application.rb文件中,半分钟后丢掉了预编译时间,但仍然很慢。

这个想法是,如果你不改变你的资产,你不需要每次重新编译它们:

这是Ben Curtis用git部署的解决scheme :

  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 

这是另一种基于资产时代的方法( 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 

如果您希望在本地预编译资产,则可以使用以下任务:

 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 

另一个有趣的方法可以是使用git钩子 。 例如,您可以将这段代码添加到.git/hooks/pre-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 

如果你决定使用这种方法,你可能需要改变你的config/environments/development.rb添加:

 config.assets.prefix = '/assets_dev' 

因此,在开发过程中,您不会提供预编译的资产。

我刚刚写了一个gem来解决Rails里的这个问题,叫turbo-sprockets-rails3 。 它可以加速您的assets:precompile仅通过重新编译已更改的文件进行assets:precompile编译,只编译一次即可生成所有资源。 它为Capistrano开箱即用,因为您的资产目录在版本之间共享。

这比使用git log的解决scheme要来得坚固得多,因为我的补丁分析了资源的来源,即使它们来自gem。 例如,如果更新jquery-rails ,则会检测到application.js的更改,只有application.js将被重新编译。

请注意,我也试图将此修补程序合并到Rails 4.0.0,可能Rails 3.2.9(请参阅https://github.com/rails/sprockets-rails/pull/21 )。 但是现在呢,如果你能帮我testing一下turbo-sprocket-rails3gem,那就太棒了,让我知道你是否有什么问题。

tommasop的解决scheme在启用cached-copy时不起作用,我的修改版本:

 task :precompile, :roles => :web, :except => { :no_release => true } do from = source.next_revision(current_revision) if capture("cd #{shared_path}/cached-copy && git diff #{from}.. --stat | grep 'app/assets' | wc -l").to_i > 0 run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{Rubber.env} #{asset_env} assets:precompile:primary} else logger.info "Skipping asset pre-compilation because there were no asset changes" end end 

您可以通过在本地系统上执行相同的操作(预编译资源)来节省您的服务器的预编译资源。 而只是移动到服务器。

 from = source.next_revision(current_revision) rescue nil if from.nil? || capture("cd #{latest_release} && #{source.local.log(from)} vendor/assets/ app/assets/ | wc -l").to_i > 0 ln_assets run_locally "rake assets:precompile" run_locally "cd public; tar -zcvf assets.tar.gz assets" top.upload "public/assets.tar.gz", "#{shared_path}", :via => :scp run "cd #{shared_path}; tar -zxvf assets.tar.gz" run_locally "rm public/assets.tar.gz" else run "ln -s #{shared_path}/assets #{latest_release}/public/assets" logger.info "Skipping asset pre-compilation because there were no asset changes" end 

Ben Curtis提出的解决scheme对我不起作用,因为在部署时(缓慢和无用),我不复制.git文件夹:

 set :scm, :git set :deploy_via, :remote_cache set :copy_exclude, ['.git'] 

我正在使用下面的代码片断,清空load 'deploy/assets'

 task :assets, :roles => :app do run <<-EOF cd #{release_path} && rm -rf public/assets && mkdir -p #{shared_path}/assets && ln -s #{shared_path}/assets public/assets && export FROM=`[ -f #{current_path}/REVISION ] && (cat #{current_path}/REVISION | perl -pe 's/$/../')` && export TO=`cat #{release_path}/REVISION` && echo ${FROM}${TO} && cd #{shared_path}/cached-copy && git log ${FROM}${TO} -- app/assets vendor/assets | wc -l | egrep '^0$' || ( echo "Recompiling assets" && cd #{release_path} && source .rvmrc && RAILS_ENV=production bundle exec rake assets:precompile --trace ) EOF end 

有时候,我需要强制跳过资产预先编译时尽快部署一个修复程序。 我使用下面的黑客作为其他答案的补充来完成这项工作。

 callback = callbacks[:after].find{|c| c.source == "deploy:assets:precompile" } callbacks[:after].delete(callback) after 'deploy:update_code', 'deploy:assets:precompile' unless fetch(:skip_assets, false) 

该脚本将更改内置的资产预编译挂钩,因此将根据skip_assets参数进行调用。 我可以调用cap deploy -S skip_assets=true来完全跳过资产预编译。

OP明确要求Capistrano,但是如果你没有专门的部署工具(通过bash脚本,Ansible剧本或类似的)进行部署,你可以使用下面的步骤加快你的Rails部署:

  • 跳过包安装
    如果安装了gem, bundle check返回1 (否则为1 ),所以如果不需要,很容易跳过捆绑安装。

  • 跳过资产预编译
    在更改之前使用git rev-parse HEAD并将当前版本的SHA存储在一个variables中(比如$previous_commit )。 然后通过git diff --name-only $previous_commit HEAD | grep -E "(app|lib|vendor)/assets"资产是否已经改变 git diff --name-only $previous_commit HEAD | grep -E "(app|lib|vendor)/assets" 。 如果返回$1 ,则可以安全地跳过资产预编译(如果使用基于发行版的部署,则可能需要将资产复制到新版本的目录中)。

  • 跳过数据库迁移
    如果您使用MySQL,请使用命令mysql --user=USER --password=PASSWORD --batch --skip-column-names --execute="USE MYAPP; SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;" 从applcation的根目录中获取最新应用迁移的名称。 将它与命令ls db/migrate | tail -1 | cut -d '_' -f 1的输出进行比较 ls db/migrate | tail -1 | cut -d '_' -f 1 ls db/migrate | tail -1 | cut -d '_' -f 1 (返回最新的可用迁移)。 如果它们不同,则需要迁移。 如果不是,则可以跳过数据库迁移。

使用Ansible进行部署的Rails开发人员可以通过在不需要的情况下将事实聚集( gather_facts: no )并使用SSHstream水线( export ANSIBLE_SSH_PIPELINING=1 )来进一步缩短部署时间。

如果你想要更多的细节,我最近写了一篇关于这个话题的文章 。