如何debuggingRails资源预编译,这是不可耐受的缓慢

我正在开发一个Rails 3.2项目,尽pipe我不认为这个项目很大,但是资产在最近几个月里增加了一些。 资产包括JS(无咖啡剧本)和SASS文件; 我们有不less的图片,但是从早期开始,它们就更less了,所以我不认为它们是一个很重要的因素。 我们可能有大约十几个库,大部分都是小的,最大的是Jquery UI JS。 部署是通过Capistrano完成的,而且开始显而易见,部署到分期的速度明显快于生产。 为了说明,同时避免了有关不同服务器和networking效应的因素,我只需在笔记本电脑上依次运行以下三个命令,如下所示:

$ 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 

所以我留下了我的脑袋。 为什么各种环境之间有如此巨大的差异? 我可以理解开发和升级之间的差距,但是我们的升级和生产configuration是相同的 。 (我应该指出生产编译将在大约2小时后完成!)

虽然最终的结果是让我的预编译变得更快,但是我想通过理解所有时间在哪里以及为什么Rails环境之间存在如此大的差异来实现这一点。 我已经看过有关使用不同压缩机的其他post,但是我找不到有关如何debugging这些rake任务的信息,以确定哪些时间正在使用,并确定哪些设置可能导致如此巨大的差异。

我不知道有什么额外的信息可能需要更新,如果和当时的意见要求。 TIA

更新:下面提供的附加信息

config/environments/production.rbconfig/environments/staging.rb (它们完全一样):

 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 

基本configuration/ application.rb是:

 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 

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 

这可能不完全回答你的问题,但我相信这是一个足够的开始。 正如你所看到的,准确的答案将取决于个人的应用程序,gem版本等。

所以。 对于与资产相关的工作,正如你所知道的,Rails使用了一个名为Sprockets的库,在Rails的新版本中,我相信Rails是Rails的一部分。 它初始化一个Sprockets“环境”,可以做一些事情,比如查看你的资产清单,加载这些文件,压缩它们,给编译的资产明智的名字等等。

默认情况下, Sprockets::Environment将其活动logging到STDERR ,日志级别为FATAL ,这在这些情况下不是很有用。 幸运的是, Sprockets::Environment (自2.2.2 )具有可写入的logging器属性,您可以使用初始化程序通过Rails进行修补。


所以,这是我的build议,开始:

config/initializers ,创build一个类似asset_logging.rb的文件。 其中,放:

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

这会覆盖默认的logging器,并将更多的信息吐出到STDOUT 。 一旦你有了这个设置,然后运行你的资产预编译任务:

 rake RAILS_ENV=production assets:precompile 

你应该看到稍微有趣的输出,比如:

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

但是,最终的答案将取决于:

  • 如何“深入”你要logging这些资产的东西
  • 你正在使用什么特定版本的Rails,Sprockets等等
  • 和你发现的一路上

正如你已经知道的那样,在Rake任务级别甚至Rails级别loginspelunking并没有提供太多的信息。 甚至使链轮本身冗长(见上文)也不会告诉你太多太多。

如果你想要比链轮更深入的话,你可能会猴子补丁的各种引擎和处理器的链轮忠实链接在一起,使资产pipe道的工作。 例如,您可以查看这些组件的日志loggingfunction:

  • Sass::Engine (将SASS转换为CSS)
  • Uglifier (JavaScript压缩包装)
  • ExecJS (在Ruby中运行JavaScript;依赖于Sprockets和Uglifier)
  • therubyracer (embedded在Ruby中的V8;由ExecJS
  • 等等

但是我会把这一切都当做“给读者一个练习”。 如果有一颗银弹,我一定想知道!

这个问题有一堆可能的原因。

对于一个可能的原因,我想知道在上次部署的几个环境中编译资产的时间是如何增加的。 这可能表明问题是在环境中还是在资产编辑本身中。 你可以使用git bisect 。 我通常会通过jenkins或其他ci系统将应用程序部署到分区,以便我可以看到部署时间和引入时间的任何变化。

它可能归结为广泛使用的资源CPU,内存(任何交换?),IO。 如果您在生产系统上编译资产,他们可能正在忙于处理您的应用程序请求。 去你的系统,做一个资源的top ,也许有太多的文件句柄在同一时间( lsof是好的)。

另一件事可能是你加载或caching你的应用程序的一些数据。 数据库通常在分段和生产环境中大得多,然后在开发箱上。 你可以在你的初始化程序或whaterver中放一些Rails.logger调用。

我想你需要查看你的Prod服务器上的CPU使用参数。

而且,资产可能有多次被预编译的可能性。 我build议在由capistrano创build的共享目录中创build一个资产目录,在部署过程中复制相同的更改并将其链接到应用程序。

如何我做到这一点,

  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