For caching a straight dump of a single already-loaded object, yes, you gain nothing or next-to-nothing. That's not what those examples are describing - they're describing a hierarchy, where any change to something lower should also trigger an update to everything higher up in the hierarchy.
The first example, from the 37signals blog, uses Project -> Todolist -> Todo
as the hierarchy. A populated example might look like this:
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)
So, let's say Bang3
was updated. All its parents also get updated:
Project: Foo (last_modified: 2014-05-16)
Todolist: Bar2 (last_modified: 2014-05-16)
Todo: Bang3 (last_modified: 2014-05-16)
Then when it comes time to render, loading Project
from the database is basically inevitable. You need a point to start at. However, because its last_modified
is an indicator of all its children, that's what you use as the cache key before attempting to load the children.
While the blog posts use separate templates, I'm going to lump them together into one. Hopefully seeing the complete interaction in one place will make it a little clearer.
So, the Django template might look something like this:
{% 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 %}
Say we pass in a Project whose cache_key
still exists in the cache. Because we propagate changes to all related objects to the parent, the fact that that particular key still exists means the entire rendered contents can be pulled from the cache.
If that particular Project had just been updated - for example, as with Foo
above - then it will have to render its children, and only then will it run the query for all Todolists for that Project. Likewise for a specific Todolist - if that list's cache_key exists, then the todos inside it have not changed, and the whole thing can be pulled from the cache.
Also notice how I'm not using todo.cache_key
in this template. It's not worth it, since as you say in the question, body
has already been pulled from the database. However, database hits aren't the only reason you might cache something. For example, taking raw markup text (such as what we type into question/answer boxes on StackExchange) and converting it to HTML may well take sufficient time that caching the result would be more efficient.
If that were so, the inner loop in the template might look more like this:
{% for todo in list.todos.all %}
{% cache 9999 todo todo.cache_key %}
<li>{{ todo.body|expensive_markup_parser }}</li>
{% endcache %}
{% endfor %}
So to pull everything together, let's go back to my original data at the top of this answer. If we assume:
- All the objects had been cached in their original state
Bang3
was just updated
- We're rendering the modified template (including
expensive_markup_parser
)
Then this is how everything would be loaded:
Foo
is retrieved from the database
Foo.cache_key
(2014-05-16) does not exist in the cache
Foo.todolists.all()
is queried: Bar1
and Bar2
are retrieved from the database
Bar1.cache_key
(2014-05-10) already exists in the cache; retrieve and output it
Bar2.cache_key
(2014-05-16) does not exist in the cache
Bar2.todos.all()
is queried: Bang3
and Bang4
are retrieved from the database
Bang3.cache_key
(2014-05-16) does not exist in the cache
{{ Bang3.body|expensive_markup_parser }}
is rendered
Bang4.cache_key
(2014-04-01) already exists in the cache; retrieve and output it
Savings from the cache in this tiny example are:
- Database hit avoided:
Bar1.todos.all()
expensive_markup_parser
avoided 3 times: Bang1
, Bang2
, and Bang4
And of course, next time it's viewed, Foo.cache_key
would be found, so the only cost to rendering is retrieving Foo
alone from the database and querying the cache.
post.body
intended to becomment.body
?