【问题标题】:Speed up assets:precompile with Rails 3.1/3.2 Capistrano deployment加速资产:使用 Rails 3.1/3.2 Capistrano 部署预编译
【发布时间】: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/…

标签: ruby-on-rails ruby-on-rails-3.1 capistrano asset-pipeline


【解决方案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 %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 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 %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
        

        【讨论】:

          【解决方案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
          

          【讨论】:

            猜你喜欢
            • 2014-02-05
            • 1970-01-01
            • 2011-12-20
            • 2013-04-29
            • 2013-11-04
            • 1970-01-01
            • 2012-04-09
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多