Comment déboguer une compilation de ressources Rails qui est incroyablement lente

Je travaille sur un projet Rails 3.2 et les actifs ont beaucoup augmenté ces derniers mois, même si je ne considère pas le projet comme important. Les actifs comprennent les fichiers JS (pas de script café) et SASS; Nous avons quelques images, mais elles sont plus nombreuses depuis le début, donc je ne pense pas qu’elles constituent un facteur important. Nous pouvons avoir une douzaine de bibliothèques et la plupart sont de petite taille, la plus grande étant Jquery UI JS. Le déploiement se fait via Capistrano et il est devenu évident que le déploiement sur le staging était nettement plus rapide que la production. Pour illustrer tout en évitant les facteurs relatifs aux différents serveurs et effets de réseau, j’ai simplement exécuté les trois commandes suivantes sur mon ordinateur portable comme suit:

$ time RAILS_ENV=production bundle exec rake assets:precomstack ^Crake aborted! [Note I aborted this run as I felt it was getting stupidly long...] real 52m33.656s user 50m48.993s sys 1m42.165s $ time RAILS_ENV=staging bundle exec rake assets:precomstack real 0m41.685s user 0m38.808s sys 0m2.803s $ time RAILS_ENV=development bundle exec rake assets:precomstack real 0m12.157s user 0m10.567s sys 0m1.531s 

Donc, je me suis laissé gratter la tête. Pourquoi y a-t-il de telles différences massives entre les différents environnements? Je peux comprendre l’écart entre le développement et la mise en scène, mais nos configurations pour la mise en scène et la production sont identiques . (Je tiens à souligner que la compilation de production se terminera après environ 2 heures!)

Bien que le résultat final soit de rendre ma précompilation plus rapide, je veux y parvenir en comprenant où tout se passe et pourquoi il y a de si grandes différences entre les environnements Rails. J’ai vu d’autres articles sur l’utilisation de différents compresseurs, par exemple, mais je ne trouve aucune information sur la façon de déboguer ces tâches afin de déterminer où le temps est passé et d’identifier les parameters susceptibles d’entraîner de telles différences.

Je ne sais pas quelles informations supplémentaires les gens peuvent avoir besoin, alors ils mettront à jour si et quand les commentaires le demandent. TIA

Mise à jour: informations supplémentaires fournies ci-dessous

config/environments/production.rb et config/environments/staging.rb (ils sont exactement les mêmes):

 MyRailsApp::Application.configure do # Code is not reloaded between requests config.cache_classes = true # Full error reports are disabled and caching is turned on config.consider_all_requests_local = false config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = true config.static_cache_control = "public, max-age=31536000" config.action_controller.asset_host = "//#{MyRailsApp::CONFIG[:cdn]}" # Compress JavaScripts and CSS config.assets.compress = true # Don't fallback to assets pipeline if a precomstackd asset is missed config.assets.comstack = false # Generate digests for assets URLs config.assets.digest = true # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found) config.i18n.fallbacks = true # Send deprecation notices to registered listeners config.active_support.deprecation = :notify end 

La base config / application.rb est:

 require File.expand_path('../boot', __FILE__) require 'rails/all' if defined?(Bundler) # If you precomstack assets before deploying to production, use this line Bundler.require(*Rails.groups(:assets => %w(development test))) # If you want your assets lazily comstackd in production, use this line # Bundler.require(:default, :assets, Rails.env) end module MyRailsApp CONFIG = YAML.load_file(File.join(File.dirname(__FILE__), 'config.yml'))[Rails.env] class Application < Rails::Application # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W(#{config.root}/lib) config.autoload_paths += %W(#{config.root}/app/workers) # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] # Enable the asset pipeline config.assets.enabled = true # Stop precompile from looking for the database config.assets.initialize_on_precompile = false # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' # Fix fonts in assets pipeline # http://stackoverflow.com/questions/6510006/add-a-new-asset-path-in-rails-3-1 config.assets.paths < true, :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") } # unless Rails.env.production? ## uncomment this 'unless' in Rails 3.1, ## because it already inserts Rack::Cache in production config.middleware.insert_after 'Rack::Cache', 'Dragonfly::Middleware', :images config.action_mailer.default_url_options = { :host => CONFIG[:email][:host] } config.action_mailer.asset_host = 'http://' + CONFIG[:email][:host] end end 

Fichier Gem:

 source 'http://rubygems.org' gem 'rails', '3.2.13' gem 'mysql2' gem 'dragonfly', '>= 0.9.14' gem 'rack-cache', :require => 'rack/cache' gem 'will_paginate' gem 'dynamic_form' gem 'amazon_product' # for looking up Amazon ASIN codes of books gem 'geoip' gem 'mobile-fu' gem 'airbrake' gem 'newrelic_rpm' gem 'bartt-ssl_requirement', '~>1.4.0', :require => 'ssl_requirement' gem 'dalli' # memcache for api_cache gem 'api_cache' gem 'daemons' gem 'delayed_job_active_record' gem 'attr_encrypted' gem 'rest-client' gem 'json', '>= 1.7.7' gem 'carrierwave' # simplify file uploads gem 'net-scp' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'therubyracer' gem 'sass-rails', '~> 3.2.3' gem 'compass', '~> 0.12.alpha' gem 'uglifier', '>= 1.0.3' gem 'jquery-fileupload-rails' end gem 'jquery-rails' gem 'api_bee', :git => 'git://github.com/ismasan/ApiBee.git', :ref => '3cff959fea5963cf46b3d5730d68927cebcc59a8' gem 'httparty', '>= 0.10.2' gem 'twitter' # Auth providers gem 'omniauth-facebook' gem 'omniauth-twitter' gem 'omniauth-google-oauth2' gem 'omniauth-identity' gem 'omniauth-readmill' gem 'bcrypt-ruby', "~> 3.0.0" # required for omniauth-identity gem 'mail_view' # To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' # Deploy with Capistrano group :development do gem 'capistrano' gem 'capistrano-ext' gem 'capistrano_colors' gem 'rvm-capistrano' # requirement for Hoof, Linux equivalent of Pow gem 'unicorn' end group :test, :development do gem 'rspec-rails' gem 'pry' gem 'pry-rails' end group :test do gem 'factory_girl_rails' gem 'capybara' gem 'cucumber-rails' gem 'database_cleaner' gem 'launchy' gem 'ruby-debug19' # Pretty printed test output gem 'shoulda-matchers' gem 'simplecov', :require => false gem 'email_spec' gem 'show_me_the_cookies' gem 'vcr' gem 'webmock', '1.6' end 

Cela ne répond peut-être pas entièrement à votre question, mais je crois que c’est un début assez décent. Comme vous le verrez, la réponse précise dépendra de l’application individuelle, des versions de gem, etc.

Alors. Comme vous le savez, pour les travaux liés aux actifs, Rails utilise une bibliothèque appelée Sprockets, qui, dans les nouvelles versions de Rails, est, je crois, connectée à Rails en tant que Railtie. Il initialise un “environnement” Sprockets qui peut faire des choses comme regarder votre manifeste d’actifs, charger ces fichiers, les compresser, donner des noms sensibles aux ressources compilées, etc.

Par défaut, Sprockets::Environment consigne son activité dans STDERR avec un niveau de journalisation FATAL , ce qui n’est pas très utile dans ces situations. Heureusement, le Sprockets::Environment (à partir de 2.2.2 ) possède un atsortingbut de journal accessible en écriture que vous pouvez utiliser via Rails, en utilisant un initialiseur.


Alors, voici ce que je suggère, pour commencer:

Dans config/initializers , créez un fichier, quelque chose comme asset_logging.rb . Dans ce document, mettez:

 Rails.application.assets.logger = Logger.new($stdout) 

Cela écrase le consignateur par défaut avec un qui crachera plus d’informations sur STDOUT . Une fois cette configuration terminée, lancez la tâche de pré-compilation de vos ressources:

 rake RAILS_ENV=production assets:precomstack 

Et vous devriez voir des résultats légèrement plus intéressants, tels que:

 ... Comstackd jquery.ui.core.js (0ms) (pid 66524) Comstackd jquery.ui.widget.js (0ms) (pid 66524) Comstackd jquery.ui.accordion.js (10ms) (pid 66524) ... 

Mais au final, la réponse finale dépendra de:

  • comment “profond” voulez-vous aller avec la consignation de ces éléments d’actif
  • quelle version spécifique de Rails, Sprockets, etc. vous utilisez
  • et ce que vous trouvez en chemin

Comme vous l’avez déjà appris, le spelunking au niveau de la tâche Rake, ou même au niveau Rails, ne fournit pas beaucoup d’informations. Et même rendre Sprockets lui-même verbeux (voir ci-dessus) ne vous dit pas trop.

Si vous voulez aller plus loin que les Sprockets, vous pouvez probablement associer des patchs aux différents moteurs et processeurs que les Sprockets associent consciencieusement pour faire fonctionner le pipeline des ressources. Par exemple, vous pouvez examiner les fonctionnalités de journalisation de ces composants:

  • Sass::Engine (convertit SASS en CSS)
  • Uglifier (enveloppe compresseur JavaScript)
  • ExecJS (exécute JavaScript dans Ruby; une dépendance à la fois des Sprockets et des Uglifier)
  • therubyracer (V8 intégré dans Ruby; utilisé par ExecJS )
  • etc.

Mais je laisserai tout cela comme “un exercice pour le lecteur”. S’il y a une balle d’argent, j’aimerais bien savoir à ce sujet!

il y a un tas de causes possibles à ce problème.

Pour une cause possible, j’aimerais savoir comment le temps de compilation des ressources a augmenté dans les différents environnements pour vos derniers déploiements. Cela peut indiquer si le problème concerne uniquement les environnements ou la compilation des ressources. vous pouvez utiliser git bisect pour cela. En général, mes applications sont déployées via un système jenkins ou un autre système ci afin que je puisse voir les variations du temps de déploiement et de leur introduction.

cela peut se résumer à une utilisation intensive des ressources CPU, MEMORY (tout échange?), IO. Si vous comstackz les actifs sur les systèmes de production, ils peuvent être occupés à répondre à la demande de vos applications. allez dans votre système, faites un top pour les ressources, peut-être qu’il ya trop de lsof fichiers en même temps ( lsof est bon pour ça).

Une autre chose pourrait être que vous chargez ou mettez en cache des données pour votre application. Les bases de données sont généralement beaucoup plus volumineuses dans les environnements de mise en scène et de production, alors qu’elles sont sur des boîtiers de développement. vous pouvez simplement placer des appels Rails.logger dans vos initialiseurs ou sur votre whaterver.

Je pense que vous devez voir les parameters d’utilisation du processeur sur votre serveur Prod.

De plus, il est possible que les actifs soient précompilés plusieurs fois. Je suggère de créer un répertoire des ressources dans le répertoire partagé créé par capistrano, en copiant vos modifications dans le même répertoire et en le liant à vos applications lors du déploiement.

Heres comment je le fais,

  after "deploy:update_code" do run "export RAILS_ENV=production" run "ln -nfs #{shared_path}/public/assets #{release_path}/public/assets" # Also for logs and temp section. # run "ln -nfs #{shared_path}/log #{release_path}/log" # run "ln -nfs #{shared_path}/tmp #{release_path}/tmp" #sudo "chmod -R 0777 #{release_path}/tmp/" #sudo "chmod -R 0777 #{release_path}/log/" end