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

Posted

技术标签:

【中文标题】加速资产:使用 Rails 3.1/3.2 Capistrano 部署预编译【英文标题】:Speed up assets:precompile with Rails 3.1/3.2 Capistrano deployment 【发布时间】:2012-02-19 10:06:58 【问题描述】:

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

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

【问题讨论】:

我不会将预编译的资产添加到 git 存储库中。你会塞满你的回购。也许这个链接可以帮助你ariejan.net/2011/09/14/… 【参考方案1】:

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

跳过捆绑安装如果有要安装的 gem,bundle check 返回1(否则为1),因此如果不需要,很容易跳过捆绑安装。

跳过资产预编译 在拉取更改之前使用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 的输出(返回最新的可用迁移)进行比较。如果它们不同,则需要迁移。如果没有,您可以跳过数据库迁移。

使用 Ansible 部署的 Rails 开发人员可以通过在不需要时关闭事实收集 (gather_facts: no) 并使用 SSH 管道 (export ANSIBLE_SSH_PIPELINING=1) 来进一步缩短部署时间。

如果你想了解更多细节,我最近写了an article关于这个话题。

【讨论】:

【参考方案2】:

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

这是使用 git 进行部署的 solution that Ben Curtis propose:

 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 %Qcd #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
        %xbundle exec rake assets:precompile
        %xrsync --recursive --times --rsh=ssh --compress --human-readable --progress public/assets #user@#host:#shared_path
        %xbundle exec rake assets:clean
      else
        logger.info 'Skipping asset pre-compilation because there were no asset changes'
      end
    end
  end
end 

另一个有趣的方法是使用 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

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

config.assets.prefix = '/assets_dev'

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

【讨论】:

喜欢这个解决方案.. 添加到我的 deploy.rb 这是很棒的东西。但如果我在 Capfile 中设置了set :copy_exclude, [ '.git' ],这将不起作用。我暂时禁用了它。如果这项任务也能尊重这一点,那就太好了。 这适用于本优秀指南中的 Unicorn deploy.rb:ariejan.net/2011/09/14/… 如果这是您的第一次部署,这将不起作用。我不得不将 if 更改为 if releases.length &lt;= 1 || capture("cd #latest_release &amp;&amp; #source.local.log(source.next_revision(current_revision)) vendor/assets/ app/assets/ | wc -l").to_i &gt; 0 检查下面的 gem (turbo-sprockets-rails3) 以获得最佳解决方案。【参考方案3】:

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

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 完全跳过资产预编译。

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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

请注意,我也在尝试将此补丁合并到 Rails 4.0.0 中,可能还有 Rails 3.2.9(请参阅https://github.com/rails/sprockets-rails/pull/21)。但是现在,如果您能帮我测试一下turbo-sprockets-rails3 gem,那就太好了,如果您有任何问题,请告诉我。

【讨论】:

是的,它适用于 SVN。此 gem 与任何版本控制工具无关。它直接在您的 Rails 应用程序中工作以更改资产功能,而不是依赖 git 或 svn。 这似乎工作得很好 - 谢谢。 Ben Curtis 的解决方案对我不起作用,因为 Capistrano 删除了 .git 目录,我懒得更改它。这是一个非常有价值的贡献 - 谢谢。 您,先生,是人中的神。谢谢!【参考方案6】:

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 %Qcd #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

【讨论】:

【参考方案7】:

solution that Ben Curtis propose 对我不起作用,因为我在部署时不复制 .git 文件夹(缓慢且无用):

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

我正在使用下面的 sn-p,没有 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

【讨论】:

以上是关于加速资产:使用 Rails 3.1/3.2 Capistrano 部署预编译的主要内容,如果未能解决你的问题,请参考以下文章

如何加快 Rails Asset Pipeline 预编译过程?

使用 Rails 为 capistrano 3.8.0 运行“cap production deploy”时不知道如何构建任务“start”

Rails 3 自动资产部署到 Amazon CloudFront?

为啥 Rails 找不到我的资产?

Rails 3 在子目录中运行,找不到资产

Rails 4.2 不创建非消化资产