{"action":"created","issue":{"url":"https://api.github.com/repos/rails/rails/issues/20752","repository_url":"https://api.github.com/repos/rails/rails","labels_url":"https://api.github.com/repos/rails/rails/issues/20752/labels{/name}","comments_url":"https://api.github.com/repos/rails/rails/issues/20752/comments","events_url":"https://api.github.com/repos/rails/rails/issues/20752/events","html_url":"https://github.com/rails/rails/issues/20752","id":92496578,"number":20752,"title":"ActionView template finder can become really slow due to disk IO","user":{"login":"elia","id":1051,"avatar_url":"https://avatars.githubusercontent.com/u/1051?v=3","gravatar_id":"","url":"https://api.github.com/users/elia","html_url":"https://github.com/elia","followers_url":"https://api.github.com/users/elia/followers","following_url":"https://api.github.com/users/elia/following{/other_user}","gists_url":"https://api.github.com/users/elia/gists{/gist_id}","starred_url":"https://api.github.com/users/elia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elia/subscriptions","organizations_url":"https://api.github.com/users/elia/orgs","repos_url":"https://api.github.com/users/elia/repos","events_url":"https://api.github.com/users/elia/events{/privacy}","received_events_url":"https://api.github.com/users/elia/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/rails/rails/labels/actionview","name":"actionview","color":"d7e102"},{"url":"https://api.github.com/repos/rails/rails/labels/pinned","name":"pinned","color":"f7c6c7"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":6,"created_at":"2015-07-01T22:13:30Z","updated_at":"2016-03-17T17:50:00Z","closed_at":null,"body":"As the number of view paths, template handlers and formats increases.\r\n\r\nCaching `ActionView::PathResolver#find_template_paths` as follows sped the rendering of an API call (rabl/json) **from 2500ms to 30ms** once the cache was warmed up:\r\n\r\n```ruby\r\nActionView::PathResolver.class_eval do\r\n  def find_template_paths(query)\r\n    Rails.cache.fetch([:action_view_path_resolver_cache, query]) do\r\n      Dir[query].reject { |filename|\r\n        File.directory?(filename) ||\r\n          # deals with case-insensitive file systems.\r\n          !File.fnmatch(query, filename, File::FNM_EXTGLOB)\r\n      }\r\n    end\r\n  end\r\nend\r\n```\r\n\r\n### Details\r\n\r\nThe app is Rails v4.1.11 / Ruby 2.2.1 / Spree 2-4-stable. I also noticed really slow performance for partial lookups relying on controller inheritance (e.g. `render 'header'` where `_header.html` is in `app/views/application`), I think it might be for the same reason.\r\n\r\nI got this results on a Spree 2.4 app with Rails 4.1.11 calling [this API endpoint](https://github.com/spree/spree/blob/2-4-stable/api/app/controllers/spree/api/states_controller.rb#L8-L20) during which `#find_template_paths` [is called 137 times](https://gist.github.com/elia/e3e624e2235f12946ea4) with queries as long as:\r\n\r\n```\r\n/Users/elia/.rvm/rubies/ruby-2.2.1/lib/ruby/gems/2.2.0/gems/kaminari-0.16.3/app/views/layouts/action_controller/base{.en,}{.json,}{.v10,.v9,.v8,.v7,.v6,.v5,.v4,.v3,.v2,.v1,}{}{.erb,.builder,.raw,.ruby,.prawn,.coffee,.opal,.haml,.rabl,.jbuilder,}\r\n```\r\n\r\nFrom what I saw not much has changed on rails@master wrt the path lookup, but I could be wrong. I agree that this can be regarded as a Spree issue but I believe that similar problems arise in many Rails apps.\r\n\r\n_just for completeness the logs of the two requests follow_\r\n\r\n\r\nWithout the patch:\r\n\r\n```\r\nStarted GET \"/api/states.json?country_id=155\" for 127.0.0.1 at 2015-07-01 23:49:58 +0200\r\nProcessing by Spree::Api::StatesController#index as JSON\r\n  Parameters: {\"country_id\"=>\"155\"}\r\n  Spree::User Load (0.5ms)  SELECT  \"spree_users\".* FROM \"spree_users\"  WHERE \"spree_users\".\"deleted_at\" IS NULL AND \"spree_users\".\"spree_api_key\" = '' LIMIT 1\r\n  Spree::Country Load (0.3ms)  SELECT  \"spree_countries\".* FROM \"spree_countries\"  WHERE \"spree_countries\".\"id\" = $1 LIMIT 1  [[\"id\", 155]]\r\n  Spree::State Load (0.2ms)  SELECT  \"spree_states\".* FROM \"spree_states\"  WHERE \"spree_states\".\"country_id\" = $1  ORDER BY name DESC LIMIT 1  [[\"country_id\", 155]]\r\n  Spree::State Load (0.4ms)  SELECT \"spree_states\".* FROM \"spree_states\"  WHERE \"spree_states\".\"country_id\" = $1  ORDER BY name ASC  [[\"country_id\", 155]]\r\n  Rendered /Users/elia/.rvm/rubies/ruby-2.2.1/lib/ruby/gems/2.2.0/bundler/gems/spree-aa554d622f47/api/app/views/spree/api/states/index.v1.rabl (1.7ms)\r\nCompleted 200 OK in 2848ms (Views: 2829.6ms | ActiveRecord: 1.9ms)\r\n```\r\n\r\nWith the patch:\r\n```\r\nStarted GET \"/api/states.json?country_id=155\" for 127.0.0.1 at 2015-07-01 23:49:05 +0200\r\nProcessing by Spree::Api::StatesController#index as JSON\r\n  Parameters: {\"country_id\"=>\"155\"}\r\n  Spree::User Load (0.7ms)  SELECT  \"spree_users\".* FROM \"spree_users\"  WHERE \"spree_users\".\"deleted_at\" IS NULL AND \"spree_users\".\"spree_api_key\" = '' LIMIT 1\r\n  Spree::Country Load (0.3ms)  SELECT  \"spree_countries\".* FROM \"spree_countries\"  WHERE \"spree_countries\".\"id\" = $1 LIMIT 1  [[\"id\", 155]]\r\n  Spree::State Load (0.2ms)  SELECT  \"spree_states\".* FROM \"spree_states\"  WHERE \"spree_states\".\"country_id\" = $1  ORDER BY name DESC LIMIT 1  [[\"country_id\", 155]]\r\n  Spree::State Load (0.4ms)  SELECT \"spree_states\".* FROM \"spree_states\"  WHERE \"spree_states\".\"country_id\" = $1  ORDER BY name ASC  [[\"country_id\", 155]]\r\n  Rendered /Users/elia/.rvm/rubies/ruby-2.2.1/lib/ruby/gems/2.2.0/bundler/gems/spree-aa554d622f47/api/app/views/spree/api/states/index.v1.rabl (1.8ms)\r\nCompleted 200 OK in 31ms (Views: 7.7ms | ActiveRecord: 2.0ms)\r\n```\r\n\r\n\r\n### Solution (?)\r\n\r\nThe above patch of course is not good (even in concept) for development where views are added and modified regularly. A possible solution could be to keep the lookup dynamic just inside the app root and cache the paths coming from gems. That in turn could be an issue for people relying on the fact that views are reloaded even for gems in development (e.g. pleople using engines) but to me seems a good trade off anyway.\r\n\r\ncc @tenderlove @masterkain\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/rails/rails/issues/comments/197998210","html_url":"https://github.com/rails/rails/issues/20752#issuecomment-197998210","issue_url":"https://api.github.com/repos/rails/rails/issues/20752","id":197998210,"user":{"login":"tenderlove","id":3124,"avatar_url":"https://avatars.githubusercontent.com/u/3124?v=3","gravatar_id":"","url":"https://api.github.com/users/tenderlove","html_url":"https://github.com/tenderlove","followers_url":"https://api.github.com/users/tenderlove/followers","following_url":"https://api.github.com/users/tenderlove/following{/other_user}","gists_url":"https://api.github.com/users/tenderlove/gists{/gist_id}","starred_url":"https://api.github.com/users/tenderlove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tenderlove/subscriptions","organizations_url":"https://api.github.com/users/tenderlove/orgs","repos_url":"https://api.github.com/users/tenderlove/repos","events_url":"https://api.github.com/users/tenderlove/events{/privacy}","received_events_url":"https://api.github.com/users/tenderlove/received_events","type":"User","site_admin":false},"created_at":"2016-03-17T17:50:00Z","updated_at":"2016-03-17T17:50:00Z","body":"Template paths should be cached in production.  Just to be clear, this is an issue in *development*, right?  I *think* we have some code to watch the FS now (/cc @matthewd), and if we can watch the FS we should know when to expire caches.  If we can get the cache to expire correctly, then I don't see a problem caching in dev."}}