This project is archived and is in readonly mode.

#5506 ✓stale
Stephen Eley

ActionView cache helper is inefficient

Reported by Stephen Eley | August 31st, 2010 @ 05:12 AM

I have a simple app that displays the schedule and session details for a large conference. With potentially hundreds of partials displayed on a single page, it was a natural candidate for fragment caching. The caching works fine, but I noticed this redundancy in the logs:

Started GET "/sessions?type=full&scope=am" for 127.0.0.1 at 2010-08-30 23:36:51 -0400
  Processing by SessionsController#index as HTML
  Parameters: {"type"=>"full", "scope"=>"am"}
Read fragment views/localhost:3000/sessions?type=full&scope=am (0.8ms)
Rendered sessions/_header.haml (2.7ms)
Rendered sessions/_search.haml (171.8ms)
Exist fragment? views/localhost:3000/sessions?id=4c5c40f0609c843161000001 (0.9ms)
Read fragment views/localhost:3000/sessions?id=4c5c40f0609c843161000001 (1.5ms)
Exist fragment? views/localhost:3000/sessions?id=4c5c40f3609c843161000003 (0.8ms)
Read fragment views/localhost:3000/sessions?id=4c5c40f3609c843161000003 (0.7ms)
Exist fragment? views/localhost:3000/sessions?id=4c5c40f6609c843161000005 (0.7ms)
Read fragment views/localhost:3000/sessions?id=4c5c40f6609c843161000005 (0.6ms)
Exist fragment? views/localhost:3000/sessions?id=4c5c40f7609c843161000006 (0.9ms)
Read fragment views/localhost:3000/sessions?id=4c5c40f7609c843161000006 (1.1ms)
Exist fragment? views/localhost:3000/sessions?id=4c5c40f9609c843161000007 (0.8ms)
Read fragment views/localhost:3000/sessions?id=4c5c40f9609c843161000007 (0.7ms)

...And so forth. My observation is that on a cache hit, the ActionView::Helpers::CacheHelper#cache method has two steps (underneath various layers of abstraction) of approximately equal duration:

  1. Call the underlying cache store's #exist? method
  2. Perform a #read to get the content

However, this is redundant because the #exist? method calls the same lower-level #read_entry method that #read does! We're retrieving the exact same value from the cache store twice. The first time we return a true or false and throw the value away; the second time, we keep the value.

This is an obvious candidate for refactoring. Every cache store has consistent behavior on #read that returns nil in the event of a cache miss. It'd be very easy to skip the existence test and simply call the execute-and-write code if the read doesn't return a value.

I'll submit a patch for this presently. I just wanted to register the issue first. For complex pages with many fragment hits, this has the potential to nearly double rendering performance.

Comments and changes to this ticket

  • José Valim

    José Valim September 1st, 2010 @ 11:29 PM

    • Importance changed from “” to “Low”

    Patch please!

  • Neeraj Singh

    Neeraj Singh September 2nd, 2010 @ 04:48 AM

    • State changed from “new” to “open”

    Stephen : nice catch.

          # Return true if the cache contains an entry for the given key.
          #
          # Options are passed to the underlying cache implementation.
          def exist?(name, options = nil)
            options = merged_options(options)
            instrument(:exist?, name) do |payload|
              entry = read_entry(namespaced_key(name, options), options)
              puts entry.inspect
              if entry && !entry.expired?
                true
              else
                false
              end
            end
          end
    

    Above method which is declared in AS cache.rb does get hold of the entire cached payload.

    However there is one thing to notice. This is an API for these storage types.

    #   config.action_controller.cache_store = :memory_store
    #   config.action_controller.cache_store = :file_store, "/path/to/cache/directory"
    #   config.action_controller.cache_store = :drb_store, "druby://localhost:9192"
    #   config.action_controller.cache_store = :mem_cache_store, "localhost"
    #   config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211")
    #   config.action_controller.cache_store = MyOwnStore.new("parameter")
    

    If you change the code in cache.rb to return the payload instead of true if there is content and use that value going forward then there is a possibility that a custom implementation of cache storage might be overriding 'def exist?' and might be returning true or false. In that case be using 'true' as the payload.

    I guess this fix is very much needed. However we should publish this change in API so that custom impelementation of cache is not caught off guard.

  • Santiago Pastorino

    Santiago Pastorino February 2nd, 2011 @ 04:49 PM

    This issue has been automatically marked as stale because it has not been commented on for at least three months.

    The resources of the Rails core team are limited, and so we are asking for your help. If you can still reproduce this error on the 3-0-stable branch or on master, please reply with all of the information you have about it and add "[state:open]" to your comment. This will reopen the ticket for review. Likewise, if you feel that this is a very important feature for Rails to include, please reply with your explanation so we can consider it.

    Thank you for all your contributions, and we hope you will understand this step to focus our efforts where they are most helpful.

  • Santiago Pastorino

    Santiago Pastorino February 2nd, 2011 @ 04:49 PM

    • State changed from “open” to “stale”

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Pages