1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 | from django.conf import settings
from django.core.cache import cache
from django.db import models
CACHE_PREFIX = settings.CACHE_MIDDLEWARE_KEY_PREFIX
CACHE_EXPIRE = 30
def cache_key(model, id):
return ('%s:%s:%s' % (CACHE_PREFIX, model._meta.db_table, id)).replace(' ', '')
class RowCacheManager(models.Manager):
"""Manager for caching single-row queries. To make invalidation easy,
we use an extra layer of indirection. The query arguments are used as a
cache key, whose stored value is the unique cache key pointing to the
object. When a model using RowCacheManager is saved, this unique cache
should be invalidated. Doing two memcached queries is still much faster
than fetching from the database."""
def get(self, *args, **kwargs):
# TODO: skip the layer of indirection if 'kwargs' contains id or pk?
id = repr(kwargs)
pointer_key = cache_key(self.model, id)
model_key = cache.get(pointer_key)
if model_key is not None:
model = cache.get(model_key)
if model is not None:
return model
# One of the cache queries missed, so we have to get the object from the database:
model = super(RowCacheManager, self).get(*args, **kwargs)
if not model_key:
model_key = cache_key(model, model.pk)
cache.set(pointer_key, model_key, CACHE_EXPIRE)
cache.set(model_key, model, CACHE_EXPIRE) # you can use 'add' instead of 'set' here
return model
class ModelWithCaching(models.Model):
def save(self, *args, **kwargs):
super(ModelWithCaching, self).save()
if kwargs.pop('invalidate_cache', True):
cache.delete(cache_key(self, self.id))
class Meta:
abstract = True
############################# Cross this line at your own risk
__metaclass__ = MetaCaching
ModelBase = type(models.Model)
class MetaCaching(ModelBase):
"""Sets ``objects'' on any model that inherits from ModelWithCaching to
be a RowCacheManager. This is tightly coupled to Django internals, so it
could break if you upgrade Django. This was done partially as a proof-of-
concept. It is advised to only use code above the comment line."""
def __new__(*args, **kwargs):
new_class = ModelBase.__new__(*args, **kwargs)
new_manager = RowCacheManager()
new_manager.contribute_to_class(new_class, 'objects')
new_class._default_manager = new_manager
return new_class
|
Comments