递归地使Django缓存中的路径无效

时间:2010-01-03 14:31:16

标签: django caching path invalidation

我正在从Django缓存中删除单个路径,如下所示:

from models                   import Graph
from django.http              import HttpRequest
from django.utils.cache       import get_cache_key
from django.db.models.signals import post_save
from django.core.cache        import cache

def expire_page(path):
    request      = HttpRequest()
    request.path = path
    key          = get_cache_key(request)
    if cache.has_key(key):   
        cache.delete(key)

def invalidate_cache(sender, instance, **kwargs):
    expire_page(instance.get_absolute_url())

post_save.connect(invalidate_cache, sender = Graph)

这有效 - 但有没有办法递归删除?我的路径看起来像这样:

/graph/123
/graph/123/2009-08-01/2009-10-21

每当保存ID为“123”的图形时,两个路径的缓存都需要无效。可以这样做吗?

3 个答案:

答案 0 :(得分:10)

您可能需要考虑采用分代缓存策略,它似乎适合您要完成的任务。在您提供的代码中,您将为每个绝对URL存储“代”号。因此,例如,您将初始化“/ graph / 123”以生成一代,然后其缓存键将变为类似“/ GENERATION / 1 / graph / 123”。如果要使该绝对URL的缓存失效,则增加其生成值(在本例中为2)。这样,下次有人去查找“/ graph / 123”时,缓存键变为“/ GENERATION / 2 / graph / 123”。这也解决了所有子页面到期的问题,因为它们应该引用与“/ graph / 123”相同的缓存键。

一开始理解它有点棘手,但它是一个非常优雅的缓存策略,如果正确完成意味着你永远不必从缓存中删除任何东西。有关更多信息,请参阅a presentation on generational caching,它适用于Rails,但概念是相同的,无论语言如何。

答案 1 :(得分:1)

另一种选择是使用支持标记密钥并按标记逐出密钥的缓存。 Django的内置缓存API不支持这种方法。但至少有一个缓存后端(不是Django本身的一部分)确实有支持。

DiskCache *是一个Apache2许可的磁盘和文件支持的缓存库,用纯Python编写,并与Django兼容。要在项目中使用DiskCache,只需安装它并配置CACHES设置。

使用pip

轻松安装
$ pip install diskcache

然后配置您的CACHES设置:

CACHES = {
    'default': {
        'BACKEND': 'diskcache.DjangoCache',
        'LOCATION': '/tmp/path/to/directory/',
    }
}

缓存set方法由可选的tag关键字参数扩展,如下所示:

from django.core.cache import cache

cache.set('/graph/123', value, tag='/graph/123')
cache.set('/graph/123/2009-08-01/2009-10-21', other_value, tag='/graph/123')

diskcache.DjangoCache在内部使用diskcache.FanoutCache。可以通过_cache属性访问相应的FanoutCache,并公开evict方法。要简单地删除标有/graph/123的所有密钥:

cache._cache.evict('/graph/123')

虽然访问下划线前缀属性可能会感觉很尴尬,但DiskCache项目是稳定的,不太可能对DjangoCache实现进行重大更改。

Django cache benchmarks页面讨论了备用缓存后端。

  • 免责声明:我是DiskCache项目的原作者。

答案 2 :(得分:-1)

结帐shutils.rmtree()os.removedirs()。我认为第一个可能是你想要的。

基于多条评论进行更新:实际上,Django缓存机制比仅使用密钥path更通用,更细粒度(尽管您可以在该级别使用它) )。我们有一些页面有7或8个单独缓存的子组件,它们会根据一系列标准到期。我们的组件缓存名称反映了关键对象(或对象类),用于标识在某些更新时需要失效的内容。

我们所有的页面都有一个基于成员/非成员状态的整体缓存密钥,但这只占页面的95%左右。其他5%可以按每个成员进行更改,因此根本不进行缓存。

如何迭代缓存以查找无效项目是如何实际存储的。如果是文件,你可以简单地使用globs和/或递归目录删除,如果它是其他机制,那么你将不得不使用别的东西。

我的答案以及其他人的一些评论试图说明你如何完成缓存失效与你使用/存储缓存的方式密切相关。

第二次更新:@andybak:所以我猜您的评论意味着我的所有商业Django网站都将在火焰中爆炸?谢谢你的提醒。我注意到你没有尝试回答这个问题。

Knipknap的问题在于他有一组出现的缓存项由于其名称而在层次结构中相关,但缓存机制的密钥生成逻辑通过创建来消除该名称路径+ vary_on的MD5哈希。由于没有原始路径/参数的痕迹,您将不得不详尽地猜测所有可能的路径/参数组合,希望您能找到合适的组。我还有其他更有趣的爱好。

如果您希望能够根据路径和/或参数值的某种组合查找缓存项目组,则必须使用可以直接进行模式匹配的缓存键或< / em>保留此信息以供搜索时使用的某些系统。

因为我们需要与OP的问题无关,所以我们在2年前就控制了模板片段缓存 - 特别是密钥生成。它允许我们以多种方式使用regexp来有效地使相关缓存项的组无效。我们还在settings.py中添加了一个默认超时和vary_on变量名称(在运行时解析),更改了名称和序列的顺序。超时,因为为了命名片段总是必须覆盖默认超时是没有意义的,使得fragment_name可解析(即它可以是变量)以更好地使用多级模板继承方案,以及其他一些的东西。

我最初的答案的唯一原因,对于当前的Django来说确实是错误的,因为我已经使用了更长时间的缓存键我真的忘记了我们离开的简单机制。