Fail Silently with Memcache Client

September 25, 2008 · 2 min read

For web applications, caching is king. I’ve recently been using memcached to cache expensive query results in a Rails application, with Seattle RB’s memcache-client as the client library.

The library is solid, but it has one opinion I disagree with: when a memcached instance fails, it throws an exception that your code has to handle. I think that’s the wrong default. When a cache fails, it doesn’t matter. Either the application continues running uncached – slower, but functional – or other memcached instances pick up the slack. Neither scenario should require special handling in application code.

Ruby, being awesome, lets me change the library’s behaviour easily. Monkey patching may be frowned upon, but it has its uses:

# A simple monkey-patch of MemCache so that broken memcached instances don't
# cause fatal errors in the application. Performance may be severely degraded
# but it should be possible to use the app anyway!
#
# A typical use would look something like:
#
#   result = if cache.alive?
#     fetch = cache.get(:foo)
#     if !fetch
#       fetch = calculate(:foo)
#       cache.set(:foo, fetch)
#     end
#     fetch
#   else
#     calculate(:foo)
#   end
#
class MemCache
  # Does the cache configuration contain any memcached instances that can
  # currently be used?
  #
  # Author: Conor Curran [http://forwind.net/]
  #
  def alive?
    !!cache.servers.detect{ |s| s.alive? }
  end

  # Rescue from MemCache::MemCacheError -- we want the cache to fail silently
  # (at least from the point of view of the application - you should still
  # monitor memcached).
  #
  def get_with_rescue(*args)
    get_without_rescue(*args)
  rescue MemCache::MemCacheError
  end
  alias_method :get_without_rescue, :get
  alias_method :get, :get_with_rescue
  alias_method :[], :get

  # Rescue from MemCache::MemCacheError -- we want the cache to fail silently
  # (at least from the point of view of the application - you should still
  # monitor memcached).
  #
  def set_with_rescue(*args)
    set_without_rescue(*args)
  rescue MemCache::MemCacheError
  end
  alias_method :set_without_rescue, :set
  alias_method :set, :set_with_rescue
  alias_method :[]=, :set
  alias_method :add, :set

  # Rescue from MemCache::MemCacheError -- we want the cache to fail silently
  # (at least from the point of view of the application - you should still
  # monitor memcached).
  #
  def delete_with_rescue(*args)
    delete_without_rescue(*args)
  rescue MemCache::MemCacheError
  end
  alias_method :delete_without_rescue, :delete
  alias_method :delete, :delete_with_rescue
end

The pattern is straightforward: wrap each method (get, set, delete) with a version that rescues MemCacheError and silently returns nil. The alias_method chain preserves the original implementation so you can still call it directly if needed.

A word of caution: “fail silently” doesn’t mean “ignore failures entirely.” You should absolutely still be monitoring your memcached instances. This patch just prevents a cache hiccup from becoming an application outage.

These posts are LLM-aided. Backbone, original writing, and structure by Craig. Research and editing by Craig + LLM. Proof-reading by Craig.