Why MemCache Makes Me Want To Spoon Out My Own Eyes

Fragment Caching is a handy technique in which you cache a fragment of your view logic for subsequent requests. This allows one to decrease the time a request takes to complete and put less load on resources. Before you even ask, no, you don’t need this on your todo list or whatever other crappy app you’re building, because odds are, you’re not going to get enough traffic to need it. Moving on. As with any decent caching system, there are multiple backends: file, memcache, etc. I have a love hate relationship with MemCache. I love it for its scalability and stupid simple use. I hate it, well, you’ll see why.

Say you actually do have traffic to your site, and you implement a fragment cache for each products star ratings:

1
2
3
<% cache "rating-#{product.id}" do %>
  <%= show_stars(product) %>
<% end %>

Simple enough, right? Now to expire:

1
expire_fragment "rating-1"

The problem, is say I need to clear ALL ratings, simple, expire_fragment takes a regexp as a key:

1
expire_fragment %r{rating-(\d+)}

Score! Not really. This works in file caching, but not in MemCache. MemCache doesn’t support an internal index (that I know of) for allowing one to iterate over keys, which is precisely what is required (if I read correctly) in order to expire based on regexp. This means I can do one of two things:

  1. Use file caching and endure a horrible IO bottleneck (no thanks).
  2. Collect all product IDs, iterate over them and expire. (no thanks).

What’s a good recommendation? I’m not sure, to be honest. One would think that, being a scalable memory store, it’d have this basic functionality, but apparently they opted against it for a performance increase. Leave me a comment with your own solution.

Related posts:

  1. Monkey Patching RubyOnRails Caching For Debugging
  2. Disabling Paperclip asset timestamp keeps you from committing genocide
  3. Inherited_resources is as close to GOD as you can get
  4. RubyOnRails Seed_fu and Paperclip woes
  5. Canonical URL tag, RubyOnRails, and why you are stupid for not using it
  • Have you seen the post by Tobias (from Shopify)? Essentially, he advocates versioning the fragments and letting the older fragments fall out of memcache.

    See http://blog.leetsoft.com/2007/5/22/the-secret-t...
  • Will look into this, thanks!
  • theRemix
    "2. Collect all product IDs, iterate over them and expire. (no thanks)."

    wait, why not? doesn't sound too painful. at least the lesser evil.
  • Considering the Memcache API that sounds very expensive: ActiveSupport::Cache::MemCacheStore#exists? tries to read a key to say whether it exists.
  • expire_fragment depends on ActiveSupport::Cache::Store#delete_matched <http://github.com/rails/rails/blob/master/activ...> which is implemented only in a very few stores. As far as I know, Memcache doesn't support such matchers. I will see whether I can implement that with my activesupport-jbosscache soon.
  • I checked the Memcache API docs <http://code.sixapart.com/svn/memcached/trunk/se...>. This API doesn't offer a command get a list of keys off all cached data. Without that it's impossible to implement #delete_matched efficiently. So I gave up to publish a hack for your problem.
  • Yeah, I didn't see anything in MemCache API either. Lame sauce. Thanks for looking though.
  • Did you have a look at TorqueBox <http://torquebox.org/>?
  • Here's the cache_key method: http://tinyurl.com/yzr37x8
  • If I'm reading your problem correctly, you could include the 'update_at' of a model in it's cache key, or a similar strategy based on your data - whats changed and what should expire etc... That way the key changes with the data so you never have to explicitly expire it nor figure out what other keys to expire.

    I do that and a bunch of other things so I don't have to expire heaps of memcache entries as data changes. Yes no regex is a pain.
  • Not really what I was getting at, but I like the idea of caching using updated_at. As for lack of regexp, yeah, totally sucks.
  • mcginniwa
    I believe you probably want to do is nested caching. The outer cache would be a wrapper for all products, inner cache would be for any individual product. It's actually something I wish was in the file store version (pretty sure it's not though).

    Now, annoyingly, I can't seem to find a good example of this in practice. However I know a guy that a presentation on it for the WellRailed group. I'll ask him to post some example code here.
  • This is one awesome missing feature, i didn't quiet understand what the hell you were talking about when you mentioned it before, but now it's perfectly clear.

    you have too much spare time... kidding, consider positing it on the memcache google group.
blog comments powered by Disqus