django model加入cache

June 12th, 2009 no comment

近来用django开发不少,对其自带自带的”django.middleware.cache.UpdateCacheMiddleware” 和”django.middleware.cache.FetchFromCacheMiddleware”感觉很不爽,原因有两个:
0,cache的过期控制只能通过超时时间进行,而不能主动通知;
1,全页面的cache粒度太粗。

遂想到django统一的models抽象应该很方便对db的cache进行统一处理,所以就有以下的想法:

目前设计的cache存储的数据结构是按db的行进行cache,cache采用memcached作为存储方式,其数据结构是:

{pointer_key -> model_key}, {model_key -> model}。

point_key:以get方法查询的表名和查询参数为基础构建,例如:’user-{‘username’:’qingran}’或者 ‘user-{‘id’:1}’;
model_key:以数据库的表名和表的primary key为基础构建,例如:’user-1’,’user-2’;
model:就是models class的内容了,在db中就是符合primary key的数据行。

这样的结构能使不同的get参数只要是查询同一个表的同一个行,那么就对应同一个model数据。

cache的命中和过期:
在get的方法,现查{point_key -> model_key},然后查询{model_key -> model}最终得到数据。如果有一步命中失败,就从db取,然后回写cache。
在models进行save和delete方法的时候把{model_key -> model}的对应删除。

但是这样的存储结构目前只能缓存models.Model.objects的get方法请求。而无法对filter方法进行cache。filter cache的难点在于这个查询的结果是一个集合,而集合中的任何一个元素的修改或者新添加一个符合filter查询的数据,都会导致cache失效。

所以说解决filter查询的cache需要解决以下问题:

0,save()和delete()方法中发生时能够快速获知filter的cache是否需要立即更新。
可能”需要手动维护所有filter相关缓存的一个key清单,当你有相关数据发生变动时手动清理相关key。”
另外新增加的符合filter查询要求的元素也会引起cache的失效。

1,因为一个filter查询集合内一个元素的失效就清理整个filter集合可能会cache的更新过于频繁。

啰嗦了一堆,上代码:
===========================================
from django.core.cache import cache
from django.db import models
from django.conf import settings

DOMAIN_CACHE_PREFIX = settings.CACHE_MIDDLEWARE_KEY_PREFIX
CACHE_EXPIRE = settings.CACHE_MIDDLEWARE_SECONDS

def cache_key(model, id):
return (“%s-%s-%s” % (DOMAIN_CACHE_PREFIX, model._meta.db_table,
id)).replace(” “, “”)

class GetCacheManager(models.Manager):
# to reload the get method. cache -> db -> cache
def get(self, *args, **kwargs):
id = repr(kwargs)

# in mc, data are stored in {pointer_key -> model_key},{model_key -> model}
# pointer_key is object.get’s method parameters.
# pointer_key = ‘www-user-{‘username’:’qingran}’ or ‘www-user-{‘id’:1}’
pointer_key = cache_key(self.model, id)

# model_key is “<prefix>-<db tablename>-pk”
# model_key = ‘www-user-1’
model_key = cache.get(pointer_key)

if model_key != None:
model = cache.get(model_key)
if model != None:
return model

# cache MISS, get from db.
model = super(GetCacheManager, self).get(*args, **kwargs)

# write data back to cache from db.
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)

return model

class ModelWithGetCache(models.Model):
# to reload the save method
def save(self, *args, **kwargs):
# first, delete cache {model_key -> model}
model_key = cache_key(self, self.pk)
cache.delete(model_key)

super(ModelWithGetCache, self).save()

# to reload the delete method
def delete(self, *args, **kwargs):
# first, delete cache {model_key -> model}
model_key = cache_key(self, self.pk)
cache.delete(model_key)

super(ModelWithGetCache, self).delete()

===============================================

在使用的时候定义models需要改从ModelWithGetCache继承,并且指定objects = GetCacheManager()

Sample:
class User(ModelWithGetCache):
objects = GetCacheManager()