web-dev-qa-db-fra.com

Accélérez vos ressources: précompilez avec le déploiement de Capistrano dans Rails 3.1/3.2

Mes déploiements sont lents, ils prennent au moins 3 minutes. La tâche lente de Capistrano pendant le déploiement est l’actif: précompiler. Cela prend probablement 99% du temps total de déploiement. Comment puis-je accélérer cela? Devrais-je précompiler mes actifs sur ma machine locale et les ajouter à mon dépôt Git?

Édition: L’ajout de config.assets.initialize_on_precompile = false à mon fichier application.rb a entraîné une demi-minute, mais il est toujours lent.

61
Godisemo

L'idée est que si vous ne modifiez pas vos actifs, vous n'avez pas besoin de les recompiler à chaque fois:

C'est la solution proposée par Ben Curtis pour un déploiement avec 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

Voici une autre approche basée sur l’âge de l’actif ( 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

Si vous préférez précompiler vos actifs localement, vous pouvez utiliser cette tâche:

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 

Une autre approche intéressante peut être l’utilisation d’un git hook . Par exemple, vous pouvez ajouter ce code à .git/hooks/pre-commit qui vérifie s’il existe des différences dans les fichiers d’actifs, puis les précompile et les ajoute au commit en cours. .

#!/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

Si vous décidez d'utiliser cette approche, vous devrez probablement modifier votre config/environments/development.rb en ajoutant:

config.assets.prefix = '/assets_dev'

Ainsi, pendant le développement, vous ne servirez pas les ressources précompilées.

84
tommasop

Je viens d'écrire un petit bijou pour résoudre ce problème à l'intérieur de Rails, appelé turbo-sprockets-Rails3 . Cela accélère votre assets:precompile en ne recompilant que les fichiers modifiés et en ne compilant qu'une seule fois pour générer tous les actifs. Cela fonctionne par défaut pour Capistrano, puisque votre répertoire d’actifs est partagé entre les versions. 

C’est bien plus efficace que les solutions qui utilisent git log, car mon correctif analyse les sources de vos ressources, même si elles proviennent d’une gemme. Par exemple, si vous mettez à jour jquery-Rails, une modification sera détectée pour application.js et seul application.js sera recompilé.

Notez que j'essaie également de fusionner ce correctif dans Rails 4.0.0 et éventuellement Rails 3.2.9 (voir https://github.com/Rails/sprockets-Rails/pull/21 ). Mais pour le moment, ce serait génial si vous pouviez m'aider à tester le turbo-sprockets-Rails3 gem, et laissez-moi savoir si vous avez un problème.

46
ndbroadbent

la solution de tommasop ne fonctionne pas si elle est activée dans cached-copy, ma version modifiée:

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

Vous pouvez enregistrer vos efforts de serveur pour la pré-compilation des actifs en procédant de la même manière (pré-compilation des actifs) sur votre système local. Et juste passer au serveur.

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

La solution proposée par Ben Curtis ne fonctionne pas pour moi car je ne copie pas le dossier .git lors du déploiement (lent et inutile):

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

J'utilise l'extrait suivant, sans 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

Il est parfois nécessaire de forcer la précompilation des actifs lors du déploiement d'un correctif au plus vite. J'utilise le hack suivant en complément d'autres réponses pour faire le travail.

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)

Ce script modifiera le raccordement intégré à la précompilation de ressources, ainsi il sera appelé en fonction du paramètre skip_assets . Je peux appeler cap deploy -S skip_assets=true pour ignorer complètement la précompilation de l’actif.

0
lulalala

L'opérateur a explicitement demandé Capistrano, mais si vous déployez sans outil de déploiement dédié (via bash script, Ansible playbook ou similaire), vous pouvez utiliser les étapes suivantes pour accélérer vos déploiements Rails:

  • Ignorer l'installation du paquet
    bundle check renvoie 1 s'il y a des gems à installer (1 sinon), il est donc facile de sauter l'installation des ensembles si cela n'est pas nécessaire.

  • Ignorer la précompilation des ressources
    Utilisez git rev-parse HEAD avant d'extraire les modifications et stockez le SHA de la version actuelle dans une variable (disons $previous_commit). Puis extrayez les modifications et découvrez si les actifs ont été modifiés avec la commande git diff --name-only $previous_commit HEAD | grep -E "(app|lib|vendor)/assets". Si cela renvoie $1, vous pouvez ignorer la précompilation des ressources en toute sécurité (si vous utilisez des déploiements basés sur les versions, vous souhaiterez peut-être copier vos ressources dans le répertoire de votre nouvelle version).

  • Ignorer les migrations de base de données
    Si vous utilisez MySQL, utilisez la commande mysql --user=USER --password=PASSWORD --batch --skip-column-names --execute="USE MYAPP; SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;" du répertoire racine de votre application pour obtenir le nom de la dernière migration appliquée. Comparez cela à la sortie de la commande ls db/migrate | tail -1 | cut -d '_' -f 1 (qui renvoie la dernière migration disponible). S'ils diffèrent, vous devez migrer. Sinon, vous pouvez ignorer les migrations de bases de données.

Les développeurs Rails déployant avec Ansible peuvent réduire davantage leur temps de déploiement en désactivant la collecte d'informations (si nécessaire) (gather_facts: no) et en utilisant le traitement en pipeline SSH (export ANSIBLE_SSH_PIPELINING=1).

Si vous voulez plus de détails, j'ai récemment écrit un article à propos de ce sujet.

0
Michael Trojanek