是的,如果要缓存单个已经加载的对象的直接转储,您将一无所获或几乎一无所获。那不是这些示例所描述的内容,而是描述层次结构,对较低层次的任何更改也应触发对层次较高层次的所有内容的更新。
来自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()
查询: Bar1
并Bar2
从数据库中检索
Bar1.cache_key
(2014-05-10)缓存中已经存在;检索并输出
Bar2.cache_key
(2014-05-16)缓存中不存在
Bar2.todos.all()
查询: Bang3
并Bang4
从数据库中检索
Bang3.cache_key
(2014-05-16)缓存中不存在
{{ Bang3.body|expensive_markup_parser }}
被渲染
Bang4.cache_key
(2014-04-01)已经存在于快取中;检索并输出
在这个小例子中,从缓存中节省的费用是:
- 避免数据库命中:
Bar1.todos.all()
expensive_markup_parser
避免3次:Bang1
,Bang2
,和Bang4
当然,下次Foo.cache_key
还会找到它,因此渲染的唯一成本是Foo
仅从数据库中检索并查询缓存。