Ruby-on-rails 加速资产:使用 Rails 3.1/3.2 Capistrano 部署进行预编译

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9016002/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 02:41:51  来源:igfitidea点击:

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

ruby-on-railsruby-on-rails-3.1capistranoasset-pipeline

提问by Godisemo

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?

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

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

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

回答by tommasop

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 proposefor a deployment with git:

这是Ben Curtis为 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) :

这是另一种基于资产年龄的方法(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-commitwhich checks if there are any differences in the assets files and eventually precompiles them and add them to the current commit.

另一个有趣的方法是使用git hook。例如,您可以添加此代码以.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

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

如果您决定使用这种方法,您可能需要更改config/environments/development.rb添加内容:

config.assets.prefix = '/assets_dev'

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

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

回答by ndbroadbent

I've just written a gem to solve this problem inside Rails, called turbo-sprockets-rails3. It speeds up your assets:precompileby 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.

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

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.jswill be recompiled.

这比使用 的解决方案更防弹git log,因为我的补丁会分析您的资产来源,即使它们来自 gem。例如,如果您更新jquery-rails,则会检测到 的更改application.js,并且只会application.js重新编译。

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-rails3gem, and let me know if you have any problems.

请注意,我还试图将此补丁合并到 Rails 4.0.0 中,可能还有 Rails 3.2.9(请参阅https://github.com/rails/sprockets-rails/pull/21)。但就目前而言,如果您能帮我测试turbo-sprockets-rails3gem 并让我知道您是否有任何问题,那就太好了

回答by yuanyiz1

tommasop's solution doesn't work when enabled cached-copy, my modified version:

tommasop 的解决方案在启用缓存副本时不起作用,我的修改版本:

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

回答by Vimal

You can save your server effort for pre-compiling assets by doing the same (pre-compiling assets) on your local system. And just moving to server.

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

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

回答by pinguin666

The solution that Ben Curtis proposedoes not work for me, because I do not copy the .git folder when deploying (slow and useless) :

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

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

I'm using the following snippet, whitout load 'deploy/assets'

我正在使用以下代码段,没有 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

回答by lulalala

There are times when I need to force skip asset precompile when deploying a fix asap. I use the following hack as a complement to other answers to do the job.

有时我需要在尽快部署修复程序时强制跳过资产预编译。我使用以下 hack 作为对其他答案的补充来完成这项工作。

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)

This script will change the built-in asset-precompile hooking, so it will be called based on the skip_assetsparameter. I can call cap deploy -S skip_assets=trueto skip asset precompile completely.

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

回答by Michael Trojanek

The OP explicitly asked for Capistrano, but in case you are deploying without a dedicated deploy tool (via bash script, Ansible playbook or similar), you may use the following steps to speed up your Rails deploys:

OP 明确要求使用 Capistrano,但如果您在没有专用部署工具的情况下进行部署(通过 bash 脚本、Ansible playbook 或类似工具),您可以使用以下步骤来加速 Rails 部署:

  • Skip bundle installation
    bundle checkreturns 1if there are gems to install (1otherwise) so it's easy to skip bundle installation if not necessary.

  • Skip asset precompilation
    Use git rev-parse HEADbefore pulling changes and store the current version's SHA in a variable (say $previous_commit). Then pull changes and find out if assets have changed with the command git diff --name-only $previous_commit HEAD | grep -E "(app|lib|vendor)/assets". If this returns $1you can safely skip asset precompilation (if you use release-based deploys you may want to copy your assets to your new release's directory).

  • Skip database migrations
    If you are using MySQL, use the command mysql --user=USER --password=PASSWORD --batch --skip-column-names --execute="USE MYAPP; SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;"from your applcation's root directory to get the name of the latest applied migration. Compare this to the output of the command ls db/migrate | tail -1 | cut -d '_' -f 1(which returns the latest available migration). If they differ, you need to migrate. If not, you can skip database migrations.

  • 如果要安装 gems,则跳过包安装
    bundle check返回11否则),因此如果不需要,很容易跳过包安装。

  • 跳过资产预编译在拉取更改之前
    使用git rev-parse HEAD并将当前版本的 SHA 存储在变量中(例如$previous_commit)。然后使用命令拉取更改并查看资产是否已更改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;"根目录中的命令来获取最新应用的迁移的名称。将此与命令的输出ls db/migrate | tail -1 | cut -d '_' -f 1(返回最新的可用迁移)进行比较。如果它们不同,则需要迁移。如果没有,您可以跳过数据库迁移。

Rails developers deploying with Ansible can further reduce their deploy time by turning facts gathering off if not needed (gather_facts: no) and use SSH pipelining (export ANSIBLE_SSH_PIPELINING=1).

使用 Ansible 进行部署的 Rails 开发人员可以通过在不需要时关闭事实收集 ( gather_facts: no) 并使用 SSH 流水线 ( export ANSIBLE_SSH_PIPELINING=1)来进一步减少他们的部署时间。

If you want more detail, I recently wrote an articleabout this topic.

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