X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=cache_utils%2Fgroup_backend.py;fp=cache_utils%2Fgroup_backend.py;h=8377477a420ae22bbbc760439ba3e704e4013307;hb=41b097a10c1ac6dcd493b030d33194003369bdf1;hp=0000000000000000000000000000000000000000;hpb=b3a391490764be17194820147b1cf47fff17b69a;p=plcapi.git diff --git a/cache_utils/group_backend.py b/cache_utils/group_backend.py new file mode 100644 index 0000000..8377477 --- /dev/null +++ b/cache_utils/group_backend.py @@ -0,0 +1,110 @@ +""" +Memcached cache backend with group O(1) invalidation ability, dog-pile +effect prevention using MintCache algorythm and project version support to allow +gracefull updates and multiple django projects on same memcached instance. +Long keys (>250) are truncated and appended with md5 hash. +""" + +import uuid +import logging +import sys +import time +from django.core.cache.backends.memcached import CacheClass as MemcachedCacheClass +from django.conf import settings +from cache_utils.utils import sanitize_memcached_key + +# This prefix is appended to the group name to prevent cache key clashes. +_VERSION_PREFIX = getattr(settings, 'VERSION', "") +_KEY_PREFIX = "_group::" + +# MINT_DELAY is an upper bound on how long any value should take to +# be generated (in seconds) +MINT_DELAY = 30 + +class CacheClass(MemcachedCacheClass): + + def add(self, key, value, timeout=0, group=None): + key = self._make_key(group, key) + + refresh_time = timeout + time.time() + real_timeout = timeout + MINT_DELAY + packed_value = (value, refresh_time, False) + + return super(CacheClass, self).add(key, packed_value, real_timeout) + + def get(self, key, default=None, group=None): + key = self._make_key(group, key) + packed_value = super(CacheClass, self).get(key, default) + if packed_value is None: + return default + value, refresh_time, refreshed = packed_value + if (time.time() > refresh_time) and not refreshed: + # Store the stale value while the cache revalidates for another + # MINT_DELAY seconds. + self.set(key, value, timeout=MINT_DELAY, group=group, refreshed=True) + return default + return value + + def set(self, key, value, timeout=0, group=None, refreshed=False): + key = self._make_key(group, key) + refresh_time = timeout + time.time() + real_timeout = timeout + MINT_DELAY + packed_value = (value, refresh_time, refreshed) + return super(CacheClass, self).set(key, packed_value, real_timeout) + + def delete(self, key, group=None): + key = self._make_key(group, key) + return super(CacheClass, self).delete(key) + + def invalidate_group(self, group): + """ Invalidates all cache keys belonging to group """ + key = "%s%s%s" % (_VERSION_PREFIX, _KEY_PREFIX, group) + super(CacheClass, self).delete(key) + + def _make_key(self, group, key, hashkey=None): + """ Generates a new cache key which belongs to a group, has + _VERSION_PREFIX prepended and is shorter than memcached key length + limit. + """ + key = _VERSION_PREFIX + key + if group: + if not hashkey: + hashkey = self._get_hashkey(group) + key = "%s:%s-%s" % (group, key, hashkey) + return sanitize_memcached_key(key) + + def _get_hashkey(self, group): + """ This can be useful sometimes if you're doing a very large number + of operations and you want to avoid all of the extra cache hits. + """ + key = "%s%s%s" % (_VERSION_PREFIX, _KEY_PREFIX, group) + hashkey = super(CacheClass, self).get(key) + if hashkey is None: + hashkey = str(uuid.uuid4()) + super(CacheClass, self).set(key, hashkey) + return hashkey + + def clear(self): + self._cache.flush_all() + +# ====================================== +# I didn't implement methods below to work with MintCache so raise +# NotImplementedError for them. + + def incr(self, key, delta=1, group=None): +# if group: +# key = self._make_key(group, key) +# return super(CacheClass, self).incr(key, delta) + raise NotImplementedError + + def decr(self, key, delta=1, group=None): +# if group: +# key = self._make_key(group, key) +# return super(CacheClass, self).decr(key, delta) + raise NotImplementedError + + def get_many(self, keys, group=None): +# hashkey = self._get_hashkey(group) +# keys = [self._make_key(group, k, hashkey) for k in keys] +# return super(CacheClass, self).get_many(keys) + raise NotImplementedError