ruby on rails - instalar - Cómo depurar una precompilación de activos de Rails que es insoportablemente lenta
ruby on rails windows 10 (3)
Estoy trabajando en un proyecto de Rails 3.2 y los activos han aumentado bastante en los últimos meses, aunque no consideraría que el proyecto sea grande. Los activos consisten en JS (sin coffee-script) y archivos SASS; tenemos bastantes imágenes pero han estado allí menos desde los primeros días, así que no creo que sean un factor importante. Podemos tener alrededor de una docena de libs y la mayoría son pequeñas, la más grande es Jquery UI JS. La implementación se realiza a través de Capistrano y comenzó a ser evidente que el despliegue en etapas fue significativamente más rápido que en la producción. Para ilustrar, evitando factores sobre diferentes servidores y efectos de red, simplemente ejecuté los siguientes tres comandos en secuencia en mi computadora portátil de la siguiente manera:
$ 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
Así que me quedé rascándome la cabeza. ¿Por qué hay tales diferencias masivas entre los diversos ambientes? Puedo entender la brecha entre desarrollo y puesta en escena, pero nuestras configuraciones para puesta en escena y producción son idénticas . (Debo señalar que la compilación de producción se completará después de aproximadamente 2 horas).
Si bien el resultado final es que mi precompilación sea más rápida, quiero lograr esto al comprender a dónde va todo el tiempo y por qué existen grandes diferencias entre los entornos de Rails. He visto otras publicaciones sobre el uso de diferentes compresores y similares, pero no puedo encontrar ninguna información sobre cómo depurar estas tareas de rake para determinar dónde se está gastando el tiempo e identificar qué configuraciones pueden estar causando tales diferencias dramáticas.
No sé qué información adicional pueden necesitar las personas, por lo que se actualizará en caso de que los comentarios se hagan. TIA
Actualización: información adicional suministrada a continuación
config/environments/production.rb
y config/environments/staging.rb
(son exactamente lo mismo):
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 es:
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
Archivo de gemas:
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
Creo que necesita ver los parámetros de uso de la CPU en su servidor Prod.
Además, puede existir la posibilidad de que los activos se precompilan varias veces. Sugiero crear un directorio de activos en el directorio compartido creado por capistrano, copiar los cambios en el mismo y vincularlo a sus aplicaciones durante la implementación.
Heres cómo lo hago,
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
Es posible que esto no responda completamente a su pregunta, pero creo que es un comienzo lo suficientemente decente. Como verá, la respuesta precisa dependerá de la aplicación individual, las versiones de gemas, etc.
Asi que. Para el trabajo relacionado con activos, como usted sabe, Rails usa una biblioteca llamada Sprockets, que en las versiones más nuevas de Rails está, creo, enganchada en Rails como una Railtie. Inicializa un "entorno" de Sprockets que puede hacer cosas como mirar el manifiesto de su activo, cargar esos archivos, comprimirlos, darle nombres compilados a los activos compilados, etc.
De forma predeterminada, Sprockets::Environment
registra su actividad en STDERR
con un nivel de registro de FATAL
, que no es muy útil en estas situaciones. Afortunadamente, Sprockets::Environment
(a partir de 2.2.2
) tiene un atributo de registro registrable que puede parchear a través de Rails, utilizando un inicializador.
Entonces, esto es lo que sugiero, para comenzar:
En config/initializers
, crea un archivo, algo así como asset_logging.rb
. En él, pon:
Rails.application.assets.logger = Logger.new($stdout)
Esto sobrescribe el registrador predeterminado con uno que escupirá más información a STDOUT
. Una vez que tenga esta configuración, ejecute su tarea de precompilación de activos:
rake RAILS_ENV=production assets:precompile
Y debería ver resultados ligeramente más interesantes, como:
...
Compiled jquery.ui.core.js (0ms) (pid 66524)
Compiled jquery.ui.widget.js (0ms) (pid 66524)
Compiled jquery.ui.accordion.js (10ms) (pid 66524)
...
Pero, al final, la respuesta final dependerá de:
- qué "profundo" quieres ir con el registro de este material
- qué versión específica de Rails, Sprockets, etc. está usando
- y lo que encuentras en el camino
Como ya aprendió, el registro de spelunking en el nivel de tarea de Rake, o incluso en el nivel de Rails, no proporciona mucha información. E incluso hacer Piñones en sí mismo detallado (ver arriba) no te dice demasiado terriblemente.
Si quieres ir más allá de las Piñones, probablemente puedas parchear los diversos motores y procesadores que Sprockets encadenan obedientemente para hacer que la tubería de activos funcione. Por ejemplo, podría examinar las capacidades de registro de estos componentes:
-
Sass::Engine
(convierte SASS a CSS) -
Uglifier
(envoltura del compresor JavaScript) -
ExecJS
(ejecuta JavaScript en Ruby, una dependencia de ambos Sprockets y Uglifier) -
therubyracer
(V8 incrustado en Ruby, utilizado porExecJS
) - etc.
Pero dejaré todo eso como "un ejercicio para el lector". ¡Si hay una bala de plata, ciertamente me gustaría saberlo!
hay muchas causas posibles para este problema.
para una posible causa, me gustaría saber cómo el tiempo de compilación de los activos aumentó en los diversos entornos para sus últimos despliegues. esto podría indicar si el problema es solo en los entornos o dentro de la compilación de activos en sí. podrías usar git bisect
para eso. Por lo general, tengo implementadas mis aplicaciones para organizarlas a través de un jenkins u otro sistema ci, de modo que pueda ver las variaciones en el tiempo de implementación y cuándo fueron presentadas.
podría reducirse a un uso extensivo de los recursos CPU, MEMORIA (¿algún intercambio?), IO. Si compila los activos en los sistemas de producción, es posible que estén ocupados atendiendo su solicitud de aplicaciones. vaya a su sistema, haga una top
de los recursos, tal vez haya demasiados archivos manejables al mismo tiempo ( lsof
es bueno para eso).
Otra cosa podría ser que cargue o guarde algunos datos para su aplicación. las bases de datos suelen ser mucho más grandes en entornos de producción y producción y luego están en cajas de desarrollo. puedes poner algunas llamadas de Rails.logger
en tus inicializadores o whaterver.