Setting tag plcapi-5.4-2
[plcapi.git] / cache_utils / group_backend.py
1 """
2 Memcached cache backend with group O(1) invalidation ability, dog-pile
3 effect prevention using MintCache algorythm and project version support to allow
4 gracefull updates and multiple django projects on same memcached instance.
5 Long keys (>250) are truncated and appended with md5 hash.
6 """
7
8 import uuid
9 import logging
10 import sys
11 import time
12 from django.core.cache.backends.memcached import CacheClass as MemcachedCacheClass
13 from django.conf import settings
14 from cache_utils.utils import sanitize_memcached_key
15
16 # This prefix is appended to the group name to prevent cache key clashes.
17 _VERSION_PREFIX = getattr(settings, 'VERSION', "")
18 _KEY_PREFIX = "_group::"
19
20 # MINT_DELAY is an upper bound on how long any value should take to
21 # be generated (in seconds)
22 MINT_DELAY = 30
23
24 class CacheClass(MemcachedCacheClass):
25
26     def add(self, key, value, timeout=0, group=None):
27         key = self._make_key(group, key)
28
29         refresh_time = timeout + time.time()
30         real_timeout = timeout + MINT_DELAY
31         packed_value = (value, refresh_time, False)
32
33         return super(CacheClass, self).add(key, packed_value, real_timeout)
34
35     def get(self, key, default=None, group=None):
36         key = self._make_key(group, key)
37         packed_value = super(CacheClass, self).get(key, default)
38         if packed_value is None:
39             return default
40         value, refresh_time, refreshed = packed_value
41         if (time.time() > refresh_time) and not refreshed:
42             # Store the stale value while the cache revalidates for another
43             # MINT_DELAY seconds.
44             self.set(key, value, timeout=MINT_DELAY, group=group, refreshed=True)
45             return default
46         return value
47
48     def set(self, key, value, timeout=0, group=None, refreshed=False):
49         key = self._make_key(group, key)
50         refresh_time = timeout + time.time()
51         real_timeout = timeout + MINT_DELAY
52         packed_value = (value, refresh_time, refreshed)
53         return super(CacheClass, self).set(key, packed_value, real_timeout)
54
55     def delete(self, key, group=None):
56         key = self._make_key(group, key)
57         return super(CacheClass, self).delete(key)
58
59     def invalidate_group(self, group):
60         """ Invalidates all cache keys belonging to group """
61         key = "%s%s%s" % (_VERSION_PREFIX, _KEY_PREFIX, group)
62         super(CacheClass, self).delete(key)
63
64     def _make_key(self, group, key, hashkey=None):
65         """ Generates a new cache key which belongs to a group, has
66             _VERSION_PREFIX prepended and is shorter than memcached key length
67             limit.
68         """
69         key = _VERSION_PREFIX + key
70         if group:
71             if not hashkey:
72                 hashkey = self._get_hashkey(group)
73             key = "%s:%s-%s" % (group, key, hashkey)
74         return sanitize_memcached_key(key)
75
76     def _get_hashkey(self, group):
77         """ This can be useful sometimes if you're doing a very large number
78             of operations and you want to avoid all of the extra cache hits.
79         """
80         key = "%s%s%s" % (_VERSION_PREFIX, _KEY_PREFIX, group)
81         hashkey = super(CacheClass, self).get(key)
82         if hashkey is None:
83             hashkey = str(uuid.uuid4())
84             super(CacheClass, self).set(key, hashkey)
85         return hashkey
86
87     def clear(self):
88         self._cache.flush_all()
89
90 # ======================================
91 # I didn't implement methods below to work with MintCache so raise
92 # NotImplementedError for them.
93
94     def incr(self, key, delta=1, group=None):
95 #        if group:
96 #            key = self._make_key(group, key)
97 #        return super(CacheClass, self).incr(key, delta)
98         raise NotImplementedError
99
100     def decr(self, key, delta=1, group=None):
101 #        if group:
102 #            key = self._make_key(group, key)
103 #        return super(CacheClass, self).decr(key, delta)
104         raise NotImplementedError
105
106     def get_many(self, keys, group=None):
107 #        hashkey = self._get_hashkey(group)
108 #        keys = [self._make_key(group, k, hashkey) for k in keys]
109 #        return super(CacheClass, self).get_many(keys)
110         raise NotImplementedError