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.
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.
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.
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
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
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
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.
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 paquetbundle 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.