Ускорить активы: предварительная компиляция с Rails 3.1 / 3.2 развертывание Capistrano

мои развертывания медленные, они занимают не менее 3 минут. Медленная задача Capistrano во время развертывания-assets:precompile. Это занимает, вероятно, 99% от общего времени развертывания. Как я могу ускорить это? Должен ли я предварительно скомпилировать свои активы на своей локальной машине и добавить их в мое РЕПО git?

Edit: Добавление config.assets.initialize_on_precompile = false Мои приложения.файл rb отбросил время предварительной компиляции на полминуты, но он все еще медленный.

7 ответов


идея заключается в том, что если вы не измените своих активов вам не нужно перекомпилировать каждый раз:

это решение, которое предлагает Бен Кертис для развертывания с 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 крюк. Например, вы можете добавить этот код .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'

так что во время разработки вы не будете обслуживать предварительно скомпилированные активы.


Я только что написал драгоценный камень, чтобы решить эту проблему внутри рельсов, называется turbo-звездочки-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-звездочки-rails3 камень, и дайте мне знать, если у вас есть какие-либо проблемы.


решение 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

вы можете сохранить усилия сервера для предварительной компиляции активов, выполнив то же самое (предварительная компиляция активов) в локальной системе. И просто переезжаю на сервер.

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

на решение, которое предлагает Бен Кертис не работает для меня, потому что я не копировать .папку git при развертывании (а) :

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

Я использую следующий фрагмент, whitout 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

бывают случаи, когда мне нужно заставить пропустить предварительную компиляцию активов при развертывании исправления как можно скорее. Я использую следующий хак в качестве дополнения к другим ответам, чтобы выполнить эту работу.

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)

этот скрипт изменит встроенное соединение asset-precompile, поэтому он будет вызываться на основе skip_assets


OP явно попросил Capistrano, но в случае развертывания без специального инструмента развертывания (через скрипт bash, Ansible playbook или аналогичный), вы можете использовать следующие шаги для ускорения развертывания Rails:

  • пропустить установку пакета
    bundle check возвращает 1 если есть драгоценные камни для установки (1 в противном случае), поэтому легко пропустить установку пакета, если это не необходимо.

  • пропустить предварительная компиляция активов
    Использовать git rev-parse HEAD перед вытягиванием изменений и сохранением SHA текущей версии в переменной (скажем $previous_commit). Затем потяните изменения и узнайте, изменились ли активы с помощью команды git diff --name-only $previous_commit HEAD | grep -E "(app|lib|vendor)/assets". Если это возвращает вы можете безопасно пропустить предварительную компиляцию активов (если вы используете развертывания на основе выпуска, вы можете скопировать свои активы в каталог нового выпуска).

  • пропустить миграцию базы данных
    Если вы используете 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, развертываемые с помощью Ansible, могут еще больше сократить время развертывания, отключив сбор фактов, если это не требуется (gather_facts: no) и использовать SSH конвейерную (export ANSIBLE_SSH_PIPELINING=1).

если вы хотите больше деталей, я недавно написал статьи об этой теме.