基于密钥的缓存如何工作?


10

我最近在37Signals博客上阅读了一篇文章,但我不知道他们如何获取缓存密钥。

具有包含对象时间戳记的缓存键是一件好事(这意味着更新对象时,缓存将失效);但是如何在模板中使用高速缓存键而又不会导致您试图从高速缓存中获取的对象受到数据库命中。

具体来说,这如何影响一对多关系(例如,您在其中呈现帖子的评论)。

Django中的示例:

{% for comment in post.comments.all %}
   {% cache comment.pk comment.modified %}
     <p>{{ post.body }}</p>
   {% endcache %}
{% endfor %}

例如,在Rails中缓存与仅对memcached的请求不同(我知道它们将您的缓存键转换为其他内容)。他们还缓存缓存密钥吗?



我已经看过了,似乎也遇到了同样的问题。为了访问缓存,需要他尝试缓存的数据。他似乎唯一要节省的是内部昂贵的操作,这与大多数此类缓存的用例不同。
多米尼克·桑托斯

没错,37signals代码也会发生这种情况,它专注于渲染代码。技巧是将整个列表也缓存在另一个容器中,或将对象的检索内容缓存在其他位置。
vdboor 2012年

实际上,他们的缓存策略似乎受过一些教育。我推荐这篇文章,以及:37signals.com/svn/posts/...
JensG

看起来您的代码段中有错别字- post.body打算是comment.body
2014年

Answers:


3

是的,如果要缓存单个已经加载的对象的直接转储,您将一无所获或几乎一无所获。那不是这些示例所描述的内容,而是描述层次结构,对较低层次的任何更改也应触发对层次较高层次的所有内容的更新。

来自37signals博客的第一个示例Project -> Todolist -> Todo用作层次结构。一个填充的示例可能如下所示:

Project: Foo (last_modified: 2014-05-10)
   Todolist:  Bar1 (last_modified: 2014-05-10)
       Todo:  Bang1 (last_modified: 2014-05-09)
       Todo:  Bang2 (last_modified: 2014-05-09)

   Todolist:  Bar2 (last_modified: 2014-04-01)
       Todo:  Bang3 (last_modified: 2014-04-01)
       Todo:  Bang4 (last_modified: 2014-04-01)

因此,可以说它Bang3已更新。它的所有父母也得到更新:

Project: Foo (last_modified: 2014-05-16)
   Todolist:  Bar2 (last_modified: 2014-05-16)
       Todo:  Bang3 (last_modified: 2014-05-16)

然后,当需要渲染时,Project从数据库进行加载基本上是不可避免的。您需要一点开始。但是,由于它last_modified所有子项的指示符,因此在尝试加载子项之前,将用作缓存键。


尽管博客文章使用单独的模板,但我将它们组合在一起。希望在一个地方看到完整的交互将使其更加清晰。

因此,Django模板可能看起来像这样:

{% cache 9999 project project.cache_key %}
<h2>{{ project.name }}<h2>
<div>
   {% for list in project.todolist.all %}
   {% cache 9999 todolist list.cache_key %}
      <ul>
         {% for todo in list.todos.all %}
            <li>{{ todo.body }}</li>
         {% endfor %}
      </ul>
   {% endcache %}
   {% endfor %}
</div>
{% endcache %}

假设我们传递了一个cache_key仍存在于缓存中的项目。因为我们将对所有相关对象的更改传播给父对象,所以该特定键仍然存在的事实意味着可以从缓存中提取整个呈现的内容

如果特定的项目刚刚被更新-例如,与Foo上面的-那么它将呈现它的孩子,只有那么它运行的所有Todolists查询该项目。同样,对于特定的待办事项列表-如果该列表的cache_key存在,则其中的待办事项没有更改,整个事情都可以从缓存中提取。

另请注意,我todo.cache_key在此模板中没有使用它。这不值得,因为正如您在问题中所说,body已经从数据库中提取了。但是,数据库命中并不是您可能缓存某些内容的唯一原因。例如,获取原始标记文本(例如我们在StackExchange上的问题/答案框中键入的内容)并将其转换为HTML可能会花费足够的时间,从而使结果的缓存效率更高。

如果是这样,则模板中的内部循环可能看起来像这样:

         {% for todo in list.todos.all %}
            {% cache 9999 todo todo.cache_key %}
               <li>{{ todo.body|expensive_markup_parser }}</li>
            {% endcache %}
         {% endfor %}

因此,将所有内容汇总在一起,让我们回到此答案顶部的原始数据。如果我们假设:

  • 所有对象均已以其原始状态缓存
  • Bang3 刚刚更新
  • 我们正在渲染修改后的模板(包括expensive_markup_parser

然后这就是所有内容的加载方式:

  • Foo 从数据库中检索
  • Foo.cache_key (2014-05-16)缓存中不存在
  • Foo.todolists.all()查询: Bar1Bar2从数据库中检索
  • Bar1.cache_key(2014-05-10)缓存中已经存在;检索并输出
  • Bar2.cache_key (2014-05-16)缓存中不存在
  • Bar2.todos.all()查询: Bang3Bang4从数据库中检索
  • Bang3.cache_key (2014-05-16)缓存中不存在
  • {{ Bang3.body|expensive_markup_parser }} 被渲染
  • Bang4.cache_key(2014-04-01)已经存在于快取中;检索并输出

在这个小例子中,从缓存中节省的费用是:

  • 避免数据库命中: Bar1.todos.all()
  • expensive_markup_parser避免3次:Bang1Bang2,和Bang4

当然,下次Foo.cache_key还会找到它,因此渲染的唯一成本是Foo仅从数据库中检索并查询缓存。


-2

如果您的示例需要对每个注释进行一些数据检索或处理,则它很好。如果您只是拿它的身体并显示它-缓存将无用。但是您可以缓存所有注释树(包括{%for%})。在这种情况下,您需要使用添加的每个注释使它无效,因此您可以将上一个注释时间戳记或注释计数放到Post中,并以此构建注释缓存键。如果您希望使用更规范化的数据,并且仅在一页上使用注释,则只需清除注释保存上的缓存键。

对我来说,将评论计数保存在Post中看起来足够好(如果您不允许删除和编辑评论)-您可以使用Post和缓存密钥在任何地方显示一个值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.