夏清然

夏清然的博客

他的个人主页  他的博客

在django models层加入简单cache

夏清然  2009年06月13日 星期六 11:37 | 4116次浏览 | 2条评论

近来用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()
    ...


评论

我的评论:

发表评论

请 登录 后发表评论。还没有在Zeuux哲思注册吗?现在 注册 !
徐继哲

回复 徐继哲  2009年06月14日 星期日 00:59

是个好注意,不过我还是倾向于进一步压榨数据库的性能,降低系统设计的复杂度。。。。:)

1条回复

暂时没有评论

Zeuux © 2024

京ICP备05028076号