问题
I have a fairly large Rails app, which uses memcached on a seperate server as its cache store.
The problem is that I randomly get errors in the production environment which seem to indicate that memcached is returning an incorrect object.
Examples:
In this example, current_site is a helper method which accesses a method on the Site model that uses Rails.cache to cache the model
ActionView::TemplateError in ListingsController#edit
undefined method `settings' for #<String:0xb565f8a0>
On line #12 of app/views/layouts/site.html.erb
9: <meta name="robots" content="noodp, all" />
10: <meta name="distribution" content="Global" />
11:
12: <% unless current_site.settings[:google_webmaster_verification_code].blank? %>
13: <meta name="verify-v1" content="<%= current_site.settings[:google_webmaster_verification_code] %>" />
14: <% end %>
15:
contrasted with....
ActionView::TemplateError in ApplicationController#not_found
undefined method `settings' for #<Category:0xd5c6c34>
On line #12 of app/views/layouts/site.html.erb
9: <meta name="robots" content="noodp, all" />
10: <meta name="distribution" content="Global" />
11:
12: <% unless current_site.settings[:google_webmaster_verification_code].blank? %>
13: <meta name="verify-v1" content="<%= current_site.settings[:google_webmaster_verification_code] %>" />
14: <% end %>
15:
When both should be returning a Site model!
Another example of cache behaving strangely:
ActionView::TemplateError in AccountsController#show
can't convert Category into String
On line #141 of app/views/layouts/site.html.erb
138: <li<%= class="first" if i == 0 %>><%= link_to top_level_category.title, top_level_category.path %></li><% end %>
139: </ul>
140: <% end %>
141: <% cache bottom_pages do %>
142: <ul><% Page.top_level.active.show_in_navigation.find(:all, :include => :slugs).each_with_index do |top_level_page, i| %>
143: <li<%= class="first" if i == 0 %>><%= link_to top_level_page.title, top_level_page.path %></li><% end %>
144: </ul>
Has anyone encountered something like this before? Anyone have thoughts on diagnosing this unreplicable problem!? I've tried switching out memcached client gems, thinking maybe it was a weird bug, but this didn't have any effect! Thanks.
回答1:
This was being caused by Passenger sharing its connection to the Memcached server. Check http://www.modrails.com/documentation/Users%20guide.html#_example_1_memcached_connection_sharing_harmful.
The fix was simply to change Passenger's Rails spawn to conservative.
回答2:
A few things that might help:
- Add instrumentation/logging to
current_siteto see exactly what is being returned. - How are you specifying keys in memcache? You could accidentally be using the same key in two different places for two different objects.
- Use
memcached-tool host:port dump > /tmp/keysto look at what's actually in your memcache. - Your memcached is behind a firewall and not exposed on a public IP, right?
回答3:
I had this problems too and was solved adding require_dependency for every class/model before the unmarshal operation by the cache provider. Maybe in production environment this is not necessary because the option: config.cache_class is setted to true, but in test and development it's false.
In fact, the Memcache (is the cache provider that I'm using) don't find the referenced class to do the unmarshal and then raise this error.
In this post has a little better solution to this problem: http://kballcodes.com/2009/09/05/rails-memcached-a-better-solution-to-the-undefined-classmodule-problem/
Regards!
回答4:
Yes, I've had this happen. With me, it was because I was doing Rails.cache.fetch(key) and key was blank.
I've done some playing around in the Rails console and with the following:
Rails.cache.read validkey # Get back the proper data
Rails.cache.fetch('') { 'abc' } # Error in rails log: 'MemCacheError ():'
Rails.cache.read validkey # Get back nil
Rails.cache.read validkey # May get back proper data
回答5:
adding a comment to this in case anyone else comes along... the kballcodes.com url is no longer valid (although you can still access it via archive.org). In the comments on that blog post, someone described a way to make Marshal try loading the object in question if it first throws the 'undefined class/module' error. I have included that code below, and referenced the original author beneath the code sample.
Add this to an initializer file in your RAILS_ROOT/config/initializers/ folder:
#
# Marshal.load is a C-method built into Ruby; because it's so low-level, it
# bypasses the full classloading chain in Ruby, in particular the #const_missing
# hook that Rails uses to auto-load classes as they're referenced. This monkey
# patch catches the generated exceptions, parses the message to determine the
# offending constant name, loads the constant, and tries again.
#
# This solution is adapted from here:
# http://kballcodes.com/2009/09/05/rails-memcached-a-better-solution-to-the-undefined-classmodule-problem/
#
class <<Marshal
def load_with_rails_classloader(*args)
begin
load_without_rails_classloader(*args)
rescue ArgumentError, NameError => e
if e.message =~ %r(undefined class/module)
const = e.message.split(' ').last
const.constantize
retry
else
raise(e)
end
end
end
alias_method_chain :load, :rails_classloader
end
All credit for this goes to Matt Brown who made this code as a pastie and commented on the now-dead article above:
来源:https://stackoverflow.com/questions/734664/rails-with-memcache-returning-wrong-cached-object