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