使多个分组缓存键失效

时间:2015-05-07 11:50:57

标签: django caching

我的模型TicketType有大约500个实例。

每周只会改变几次。

但是如果它改变了,我需要使用旧TicketTypes的所有缓存值无效。

不幸的是,某些缓存键并未修复。它们包含计算数据。

我看到了这些解决方案:

使用version参数并更新TicketType的保存后信号处理程序的版本值。

对所有基于TicketType的缓存键使用公共前缀。 然后使后保存信号处理程序中的所有缓存键无效。

我想还有第三种,更好的方法......

示例:

TicketType是一棵树。 TicketTypes的可见性绑定到权限。如果树具有不同的权限,则两个用户可能会以不同的方式查看树。我们根据权限缓存它。用户的权限被序列化和散列。通过创建包含散列和固定部分的字符串来创建缓存键:

hash_key='ticket-type-tree--%s' % hashed_permissions

如果TicketType树发生更改,我们需要确保没有从缓存中加载旧数据。只要不使用旧数据,就不需要主动无效。

4 个答案:

答案 0 :(得分:1)

您可以将故障单修改时间用作缓存密钥的一部分。

hash_key = 'ticket-type-tree--%s-%s' % (hashed_permissions, tree.lastmodified)

您可以使用DateTimeField添加auto_now=True。如果从db获取修改时间过于昂贵,您也可以缓存它。

通常,更新post_save信号处理程序中的缓存很好。除非您希望始终拥有一致的数据,并希望为交易支付额外费用。

答案 1 :(得分:1)

使用redis缓存模型

我将缓存我的实例的方式如下:

1 - 确保您当时正在购买一件商品。例如:Model.objects.get(foo ='bar'),每次从数据库中获取模型时都使用属性foo。这将用于确保数据在以后失效。

2-Override方法save()并确保使用foo属性将数据保存到缓存中。

E.g:

class Model(model.Model):
    foo = models.CharField()
    bar = models.CharField()

    def save(self, *args, **kwargs):
        redis.set(foo, serialize_model())
        super(Model, self).save(*args, **kwargs)

    def serialize_model():
        return serilized_object

3-Override get方法在命中数据库之前获取序列化对象。

E.g:

class Model(model.Model):
    ...
    def get(self, *args, **kwargs):
        if redis.get(self.foo):
            return redis.get(self.foo)
        else:
            return super(Model).get(*args, **kwargs)

4 - 如果删除或删除实例,则覆盖删除方法以删除缓存

E.g

class Model(model.Model):
    ...
    def delete(self,*args, **kwargs):
        redis.delete(self.foo)
        super(Model, self).delete(*args, **kwargs)

将Model类替换为您的模型,在这种情况下,它将是Ticket Type

有一件事,我假设您不会在Django应用程序之外触摸数据库。如果你在任何其他地方使用原始sql,这将无效。

在他们的网站上查找redis功能,它们具有删除,设置和获取功能。如果你正在使用其他缓存方式。寻找如何设置,获取和删除。

答案 2 :(得分:0)

好吧,基本上你的问题只是缓存键的表现力。当你必须做一些像哈希一样复杂的东西来获取密钥时,它必须是一个暗示有缺失的东西。

在你的情况下,我相信缺少的只是一个"权限集"宾语。你可以把它称为一个组,一个角色(如在RBAC中)...那就是为什么我问你集合是否重复 - 实际上,你的哈希键只是一种重新创建设置对象的ID的方法不存在。

所以解决方案是:

  • 创建一个角色模型,M2M依赖于用户,M2M依赖于权限(根据我的理解,它与您的TicketTypes相关联)
  • 使用事件处理程序捕获保存到TicketType。
    • 获取所有受影响的角色(通​​过权限)
    • 生成密钥(类似于ticket-type-TREEID-ROLEID)并使其无效

两个最后的评论:

  • 有时候cache.clear()就是解决方案 - 特别是如果你不使用缓存做其他事情
  • 您说在导航树时您的SQL查询计数很大。如果你还没有尝试过,你可能只想用prefetch和select_related来优化它(参见文档)。

答案 3 :(得分:0)

在TicketType保存信号处理程序中:
a)根据所有用户的权限生成密钥并使密钥无效
b)为每个排列(许可)生成密钥(如果可以计算它们)并使密钥无效
c)使用第二个memcached实例仅存储这些缓存并清除它(最简单)

P.S。:专业提示将刷新缓存而不是使它们无效。然而,django信号中未被捕获的异常可能会有麻烦,所以要疲倦