加速资产:使用 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 <= 1 || capture("cd #latest_release && #source.local.log(source.next_revision(current_revision)) vendor/assets/ app/assets/ | wc -l").to_i > 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”