web-dev-qa-db-fra.com

Comment déboguer une précompilation d'actif Rails qui est insupportablement lente

Je travaille sur un projet Rails 3.2 et les actifs ont augmenté un peu ces derniers mois, bien que je ne considère pas le projet comme important. Les actifs sont constitués de JS (pas de café- script), et les fichiers SASS; nous avons pas mal d'images mais elles y sont moins depuis les premiers jours donc je ne pense pas qu'elles soient un facteur substantiel. Nous pouvons avoir une douzaine de libs et la plupart sont petites, le plus grand est Jquery UI JS. Le déploiement se fait via Capistrano et il est devenu évident que le déploiement vers la mise en scène était beaucoup plus rapide que vers la production. mon ordinateur portable comme suit:

$ time Rails_ENV=production bundle exec rake assets:precompile
^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:precompile
real    0m41.685s
user    0m38.808s
sys 0m2.803s

$ time Rails_ENV=development bundle exec rake assets:precompile
real    0m12.157s
user    0m10.567s
sys 0m1.531s

Alors je me suis gratté la tête. Pourquoi existe-t-il des différences aussi importantes 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 dois souligner que la compilation de la production se terminera après environ 2 heures!)

Alors que le résultat final accélère ma précompilation, je veux y parvenir en comprenant où va tout le temps 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 et autres, mais je ne trouve aucune information sur la façon de déboguer ces tâches de râteau pour déterminer où le temps est passé et identifier les paramètres qui peuvent causer de telles différences dramatiques.

Je ne sais pas quelles informations supplémentaires les gens peuvent avoir besoin, donc 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 precompiled asset is missed
  config.assets.compile = 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 precompile assets before deploying to production, use this line
  Bundler.require(*Rails.groups(:assets => %w(development test)))
  # If you want your assets lazily compiled 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 << Rails.root.join('app','assets','fonts')

    config.middleware.insert 0, 'Rack::Cache', {
      :verbose     => 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
40
arooaroo

Cela ne répond pas entièrement à votre question, mais je pense 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 gemmes, etc.

Donc. Pour le travail lié aux actifs, comme vous le savez, Rails utilise une bibliothèque appelée Sprockets, qui dans les versions plus récentes 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 aux actifs compilés des noms sensés, etc.

Par défaut, ce Sprockets::Environment Enregistre son activité dans STDERR avec un niveau de journal de FATAL, ce qui n'est pas très utile dans ces situations. Heureusement, le Sprockets::Environment (À partir de 2.2.2) Possède un attribut de logger inscriptible que vous pouvez patcher 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.config.assets.logger = Logger.new($stdout)

Cela écrase l'enregistreur par défaut avec un qui enverra plus d'informations à STDOUT. Une fois que vous avez configuré cela, exécutez votre tâche de pré-compilation des ressources:

rake Rails_ENV=production assets:precompile

Et vous devriez voir légèrement une sortie plus intéressante, comme:

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

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

  • à quel point vous voulez aller avec la journalisation de ces éléments d'actif
  • quelle version spécifique de Rails, Sprockets, etc. que vous utilisez
  • et ce que vous trouverez en cours de route

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

Si vous vouliez aller plus loin que Sprockets, vous pouvez probablement corriger les différents moteurs et processeurs que Sprockets enchaîne consciencieusement pour faire fonctionner le pipeline d'actifs. Par exemple, vous pouvez examiner les capacités de journalisation de ces composants:

  • Sass::Engine (Convertit SASS en CSS)
  • Uglifier (wrapper de compresseur JavaScript)
  • ExecJS (exécute JavaScript dans Ruby; une dépendance à la fois de Sprockets et d'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 solution miracle, j'aimerais certainement le savoir!

33
GladstoneKeep

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

pour une cause possible, j'aimerais savoir comment le temps de compilation des actifs 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 d'actifs elle-même. vous pourriez utiliser git bisect pour ça. j'ai généralement mes applications déployées pour la mise en scène via un jenkins ou un autre système ci afin que je puisse voir toutes les variations de temps de déploiement et quand elles ont été introduites.

cela peut se résumer à une utilisation intensive des ressources CPU, MEMORY (tout échange?), IO. si vous compilez les actifs sur les systèmes de production, ils pourraient être occupés à répondre à votre demande d'applications. allez sur votre système, faites un top pour les ressources, peut-être qu'il y a trop de descripteurs de fichiers en même temps (lsof est bon pour ça).

une autre chose peut être que vous chargez ou mettez en cache certaines données pour votre application. les bases de données sont généralement beaucoup plus volumineuses dans les environnements de production et de transfert que dans les boîtes de développement. vous pouvez simplement mettre un peu de Rails.logger appelle dans vos initialiseurs ou whaterver.

2
phoet

Je pense que vous devez voir les paramètres d'utilisation du processeur sur votre serveur Prod.

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

Voici 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
2
Aditya