Patching Django’s Memcached cache.set() Method

Django’s memcached set method looks innocuous enough:

    def set(self, key, value, timeout=0, version=None):
        key = self.make_key(key, version=version)
        self._cache.set(key, value, self._get_memcache_timeout(timeout))

However it suppresses the return value of the underlying call to memcached’s set command. In most cases this is fine – calls to memcached usually never fail. However, in those rare cases where something goes wrong, it makes debugging far more difficult. I recently had one of those “This cannot happen” moments, which ultimately turned out to be downstream of a silent failed cache.set() call. It would be much nicer if the set call either threw an exception or returned an error if the set failed. (I suggested this to the django maintainers a while back but was summarily rejected.)

Here are two ways to work around this. The first is to patch the set method. Just put this somewhere in your code:

def patched_memcached_set(self, key, value, timeout=0, version=None):
    key = self.make_key(key, version=version)
    return self._cache.set(key, value, self._get_memcache_timeout(timeout))

CacheClass.set = patched_memcached_set

Now you can check the return value of cache.set() – True means success, False means failure.

from django.core.cache import cache
import logging

if not cache.set(key, value):
    logging.error('Failed to set key/val %s/%s in memcache!', key, value)
    # Recovery code

The second way is to use your own cache backend.

class MemcachedCache(BaseMemcachedCache):
    """A (slightly hacked) implementation of a cache binding using python-memcached"""
    def __init__(self, server, params):
        import memcache

        super(MemcachedCache, self).__init__(server, params,
                                             library=memcache,
                                             value_not_found_exception=ValueError)

    # Return the value of set so we can tell if the set was successful or not.
    def set(self, key, value, timeout=0, version=None):
        key = self.make_key(key, version=version)
        return self._cache.set(key, value, self._get_memcache_timeout(timeout))

This time you’ll need to do one more thing – install the cache backend in your settings.py file:

CACHES = {
    'default': {
        #'BACKEND' : 'django.core.cache.backends.memcached.MemcachedCache',
        'BACKEND' : 'apps.utils.memcached.MemcachedCache',
        'LOCATION': [
            '10.1.1.101:11211',
            '10.1.1.102:11211',
            '10.1.1.103:11211',
        ],
    }
}

Easy enough. Now, as before, you can check the return value of cache.set() to see if your set actually succeeded.

I strongly recommend doing one of these options if you’re running django and memcached. It’ll save you a ton of time debugging when you hit the rare case of cache.set() failing.

This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

2 Responses to Patching Django’s Memcached cache.set() Method

  1. freakboy3742 says:

    I think you might need to go back and re-read the comment when your Django ticket was closed. Your ticket wasn’t “summarily rejected” at all. It’s been closed “NeedsInfo” — in this case, with a pretty clear message as to what “info” is required (a review of whether we can add this to other backends).

    I completely understand why Luke closed the ticket. If he had left it open, the most likely outcome (based on aggregate behaviour from other tickets) is that it would sit there untouched for several years. The “NeedsInfo” close state exists specifically for handling tickets of this kind — to provide us a way to close out a ticket, while leaving open the door for the original reporter to reopen with more information (information that would hopefully progress the ticket towards another outcome).

    I’d also add that if you ever disagree with a a core developers decision in closing a ticket, you are encouraged to take up the issue on django-developers. We’re not superhuman, and we sometimes make mistakes. It’s also possible that some of the core team will disagree with the person who closed the ticket.

    So – if you’re keen about seeing this fix in Django trunk, pursue the issue! Provide the info that has been requested, and reopen the ticket (which is the procedure documented as part of our contributions guide https://docs.djangoproject.com/en/dev/internals/contributing/triaging-tickets/#closing-tickets). I, for one, can certainly see the value in what you’re proposing (and I’d rather people didn’t have to monkey patch to get the capability).

    • Russell,

      Thanks for this comment. I apologize if “summarily rejected” was too strong a term. Not knowing the bug submission process for django it seemed like my ticket was closed rather abruptly and without much consideration. I’m sure you guys get tons of bug reports daily which can be somewhat annoying to wade through. Aside from memcached I haven’t used the other cache backends that Luke mentions. The django devs have a reputation for being a tough group to broach, so rather than get into an argument I figured I’d just post my suggestion on a blog, which I’ve finally gotten around to doing.

      Russell

Leave a comment