web-dev-qa-db-ja.com

アセットの高速化:Rails 3.1 / 3.2 Capistranoデプロイメントでプリコンパイル

私の展開は遅く、少なくとも3分かかります。デプロイ中の遅いCapistranoタスクは、assets:precompileです。これにはおそらく、合計展開時間の99%がかかります。これをどのようにスピードアップできますか?ローカルマシンでアセットをプリコンパイルし、gitリポジトリに追加する必要がありますか?

編集:config.assets.initialize_on_precompile = falseをapplication.rbファイルに追加すると、プリコンパイル時間が30分短縮されましたが、依然として遅いです。

61
Godisemo

アイデアは、アセットを変更しなければ、毎回それらを再コンパイルする必要がないということです:

これは 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

資産の年齢に基づいた別のアプローチ( 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'

そのため、開発中にプリコンパイルされたアセットを提供しません。

84
tommasop

turbo-sprockets-Rails と呼ばれる、Rails内でこの問題を解決するgemを作成しました。変更されたファイルを再コンパイルするだけで、すべてのアセットを生成するために一度だけコンパイルすることにより、assets:precompileを高速化します。アセットディレクトリはリリース間で共有されるため、Capistranoの場合はそのまま使用できます。

これは、git logを使用するソリューションよりもはるかに安全です。なぜなら、私のパッチは、gemからのものであっても、アセットのソースを分析するからです。たとえば、jquery-Railsを更新すると、application.jsの変更が検出され、application.jsのみが再コンパイルされます。

また、このパッチをRails 4.0.0、そしておそらくRails 3.2.9にマージしようとしていることに注意してください( https://github.com/ Rails/sprockets-Rails/pull/21 )。しかし、今のところ、 turbo-sprockets-Rails gemのテストを手伝ってくれて、何か問題があればお知らせください。

46
ndbroadbent

キャッシュコピーを有効にすると、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
4
yuanyiz1

ローカルシステムで同じ(アセットのプリコンパイル)を実行することで、アセットのプリコンパイルのサーバー作業を節約できます。そして、サーバーに移動します。

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
3
Vimal

Ben Curtisが提案する解決策 は機能しません。デプロイ時に.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
2
pinguin666

修正をできるだけ早く展開するときに、アセットのプリコンパイルを強制的にスキップする必要がある場合があります。私は、仕事をするために他の答えを補完するものとして、次のハックを使用します。

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を呼び出して、アセットのプリコンパイルを完全にスキップできます。

0
lulalala

OPは明示的にCapistranoを要求しましたが、専用の展開ツールを使用せずに展開する場合(bashスクリプト、Ansibleプレイブックなどを使用)、次の手順を使用してRails deploys :

  • バンドルのインストールをスキップする
    _bundle check_は、インストールするgemがある場合は_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_)を使用することで、デプロイ時間をさらに短縮できます。

さらに詳細が必要な場合は、最近このトピックについて 記事 を書きました。

0