Как работает кэширование на основе ключей?

10

Недавно я прочитал статью в блоге 37Signals, и мне интересно, как они получают ключ кеша.

Хорошо, когда у вас есть ключ кеша, который включает временную метку объекта (это означает, что при обновлении объекта кеш будет признан недействительным); но как тогда использовать ключ кеша в шаблоне, не вызывая попадания в БД для самого объекта, который вы пытаетесь извлечь из кеша.

В частности, как это влияет на отношения Один-ко-многим, например, когда вы отображаете Комментарии к Посту.

Пример в Джанго:

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

Кеширование в Rails отличается от просто запросов к memcached, например (я знаю, что они конвертируют ваш ключ кеша во что-то другое). Они тоже кешируют ключ кеша?

Доминик Сантос
источник
Взгляните на rossp.org/blog/2012/feb/29/fragment-caching для примера Django!
vdboor
Я уже посмотрел на это, и, похоже, страдает от той же проблемы. Данные, которые он пытается кешировать, необходимы для доступа к кешу. Единственное, на чем он, похоже, экономит, это внутренняя дорогая операция, которая не похожа на большинство случаев использования для этого типа кэширования.
Доминик Сантос
Это правда, также происходит с кодом 37signals, он сосредоточен на коде рендеринга. Хитрость заключается в том, чтобы кэшировать весь список в другом контейнере или кэшировать извлечение объекта в другом месте.
vdboor
На самом деле их стратегия кэширования кажется немного более образованной. Я также рекомендую эту статью: 37signals.com/svn/posts/…
JensG
Похоже, что в вашем фрагменте кода есть опечатка - это было post.bodyзадумано comment.body?
Изката

Ответы:

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выше - тогда он должен будет отобразить своих потомков, и только тогда он выполнит запрос для всех Todolist для этого проекта. Аналогично для конкретного Todolist - если существует ключ 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одиночку из базы данных и запрос кеша.

Izkata
источник
-2

Ваш пример хорош, если для каждого комментария требуется поиск или обработка данных. Если вы просто возьмете тело и отобразите его - кеш будет бесполезным. Но вы можете кэшировать все дерево комментариев (включая {% для%}). В этом случае вам нужно аннулировать его с каждым добавленным комментарием, чтобы вы могли поместить метку времени последнего комментария или счетчик комментариев где-нибудь в Post и построить ключ кеша комментариев с ним. Если вы предпочитаете более нормализованные данные и используете комментарии только на одной странице, вы можете просто очистить ключ кэша при сохранении комментария.

Для меня сохранение количества комментариев в Посте выглядит достаточно хорошо (если вы не разрешаете удалять и редактировать комментарии) - у вас есть значение, которое нужно показать где-либо с Постом, и ключ для кеширования.

ilvar
источник