web-dev-qa-db-ja.com

耐え難いほど遅いRailsアセットのプリコンパイルをデバッグする方法

私はRails 3.2プロジェクトに取り組んでおり、プロジェクトは大規模とは思わないが、ここ数か月で資産はかなり増加している。資産はJS(コーヒーなし-スクリプト)、およびSASSファイル、かなりの数の画像がありますが、初期の頃からあまり存在していなかったため、それらは実質的な要因ではないと思います。最も大きいのはJquery UI JSです。展開はCapistranoを介して行われ、ステージングへの展開は実稼働よりもはるかに高速であることが明らかになり始めました。異なるサーバーとネットワーク効果に関する要因を回避しながら、次のように私のラップトップ:

$ 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

だから私は頭をかきむしりました。さまざまな環境の間になぜそんなに大きな違いがあるのですか?開発とステージングのギャップは理解できますが、ステージングとプロダクションの設定は同じです。 (本番コンパイルは約2時間後に完了することに注意してください!)

最終結果は私のプリコンパイルを高速化することですが、常にどこに行くのか、なぜRails環境の間に大きな違いがあるのか​​を理解することでこれを達成したいと思います。さまざまなコンプレッサーの使用に関する他の投稿などがありますが、これらのレーキタスクをデバッグして時間を費やしている場所を見つけ、どの設定がそのような劇的な違いを引き起こしているのかを特定する方法に関する情報を見つけることができません。

私は、人々が必要とするかもしれない追加情報を知らないので、コメントが尋ねられたら、いつでも更新します。 TIA

更新:以下に提供される追加情報

config/environments/production.rbおよびconfig/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

ベースのconfig/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

宝石ファイル:

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

これは完全にあなたの質問に答えないかもしれませんが、それはまともなスタートだと思います。ご覧のとおり、正確な答えは個々のアプリケーション、gemのバージョンなどによって異なります。

そう。資産関連の作業では、ご存じのとおり、RailsはSprocketsと呼ばれるライブラリを使用します。これは、Railsの新しいバージョンではRailsにフックされますRailtie。アセットマニフェストの確認、それらのファイルのロード、圧縮、コンパイルされたアセットの意味のある名前の付与などを行うことができるスプロケットの「環境」を初期化します。

デフォルトでは、そのSprockets::EnvironmentはそのアクティビティをSTDERRのログレベルでFATALに記録しますが、これらの状況ではあまり役に立ちません。幸いなことに、Sprockets::Environment2.2.2現在)には、イニシャライザーを使用してRails経由でパッチできる書き込み可能なロガー属性があります。


だから、ここに私が提案するものがあります:

config/initializersで、asset_logging.rbのようなファイルを作成します。その中に、以下を入れてください:

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

これにより、デフォルトのロガーが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)
...

しかし、最終的には、最終的な答えは次の要素に依存します。

  • このアセットをログに記録する方法の「深さ」
  • 使用しているRails、スプロケットなどの特定のバージョン
  • そしてあなたが道に沿って見つけるもの

既に学んだように、Rakeタスクレベル、またはRailsレベルでのログスペルチェックでは、多くの情報は提供されません。また、スプロケット自体を冗長にしたとしても(上記を参照)、それほどひどくは伝えません。

スプロケットよりも深くしたい場合は、おそらく、スプロケットが忠実にチェーン接続してエンジンパイプラインを機能させるさまざまなエンジンとプロセッサーにパッチを当てることができます。たとえば、次のコンポーネントのロギング機能を調べることができます。

  • Sass::Engine(SASSをCSSに変換)
  • Uglifier(JavaScriptコンプレッサーラッパー)
  • ExecJS(RubyでJavaScriptを実行します。スプロケットとUglifierの両方の依存関係)
  • therubyracer(Rubyに埋め込まれたV8。ExecJSで使用)
  • 等.

しかし、私はそれをすべて「読者のための運動」のままにします。特効薬がある場合、私は確かにそれについて知りたいです!

33
GladstoneKeep

この問題には多くの原因が考えられます。

考えられる原因のために、最後のデプロイのいくつかの環境でアセットのコンパイル時間がどのように増加したかを知りたいと思います。これは、問題が単に環境にあるのか、アセットのコンパイル自体にあるのかを示している可能性があります。そのためにgit bisectを使用できます。私は通常、ジェンキンスまたは別のCIシステムを介してステージングにアプリを展開しているため、展開時間とそれらが導入されたときの変動を確認できます。

リソースCPU、MEMORY(任意のスワップ?)、IOの広範な使用に要約される可能性があります。実稼働システムでアセットをコンパイルすると、アプリケーションリクエストの処理に忙しくなります。システムにアクセスして、リソースに対してtopを実行します。同時に、ファイルハンドルが多すぎる場合があります(lsofが適切です)。

もう1つは、アプリケーションのデータをロードまたはキャッシュすることです。データベースは通常、ステージング環境および運用環境では開発ボックスよりもはるかに大きくなります。イニシャライザまたはその他にいくつかのRails.logger呼び出しを置くことができます。

2
phoet

ProdサーバーのCPU使用パラメーターを確認する必要があると思います。

さらに、アセットが複数回プリコンパイルされる可能性があります。 capistranoによって作成された共有ディレクトリにアセットディレクトリを作成し、変更を同じディレクトリにコピーして、デプロイ中にアプリにリンクすることをお勧めします。

私がそれをする方法はここにあります、

  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