it-swarm.com.ru

Ускорьте ресурсы: прекомпилируйте с Rails 3.1 / 3.2 Развертывание Capistrano

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

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

61
Godisemo

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

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

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

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

решение, которое предлагает Бен Кертис не работает для меня, потому что я не копирую папку .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 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". Если при этом возвращается $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 (которая возвращает последнюю доступную миграцию). Если они отличаются, вам нужно мигрировать. Если нет, вы можете пропустить миграцию базы данных.

Разработчики Rails, внедряющие с помощью Ansible, могут еще больше сократить время их развертывания, отключив сбор фактов, если в этом нет необходимости (gather_facts: no), и используют конвейерную передачу SSH (export ANSIBLE_SSH_PIPELINING=1).

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

0
Michael Trojanek