Условное кэширование при просмотре блоков в Django
На нашем сайте мы просматриваем блоки wagtail для многих наших веб-страниц. Некоторые из этих блоков содержат веб-формы или динамические/персонализированные данные, и поэтому не могут быть кэшированы. Мы хотим обеспечить максимальную гибкость для редакторов, чтобы они могли обновлять веб-страницы, не прикасаясь к коду, но при этом обеспечить хорошую производительность и время загрузки.
В приведенном ниже коде поле page.body представляет собой поле потока wagtail, в которое редакторы могут добавлять любое количество блоков контента и в любом порядке.
Вот желаемое решение, с упрощенным кодом, которое не работает:
{% load cache wagtailcore_tags %}
<!-- Start the page cache -->
{% cache timeout url_path %}
{% for block in page.body %}
<!-- disable the cache before an excluded block -->
{% if block.disable_cache %}
{% endcache %} <!-- template engine throws an error at this "endcache" tag -->
{% endif %}
{% include_block block %}
<!-- reenable the cache after an excluded block is rendered -->
{% if block.disable_cache %}
{% cache timeout block.uuid %}
{% endif %}
{% endfor %}
<!-- end the page cache -->
{% endcache %}
Допустим, страница состоит из 15 блоков, причем один блок в середине содержит форму, которую не следует кэшировать. В этом случае у нас будет 1 отрисованный блок между 2 кэшированными наборами блоков, что приведет к 2 обращениям к кэшу. Проблема заключается в том, что рендерер шаблона не принимает и не анализирует тег cache внутри условия, поэтому это решение не работает с ошибкой, когда шаблонизатор ожидает тег "endif" там, где в коде появляется первый "endcache".
Альтернативой может быть кэширование каждого блока по отдельности, что работает следующим образом:
{% load cache wagtailcore_tags %}
{% for block in page.body %}
<!-- render or cache each block individually -->
{% if block.disable_cache %}
{% include_block block %}
{% else %}
{% cache timeout block.uuid %}
{% include_block block %}
{% endcache %}
{% endif %}
{% endfor %}
Хотя это второе решение работает, оно приводит к рендерингу 1 блока и 14 обращениям к кэшу, по одному для каждого отдельного блока. Очевидно, что это гораздо менее производительно, чем первое (неудачное) решение.
У кого-нибудь есть мысли или опыт по уменьшению количества обращений к кэшу при использовании фрагмента cahce и циклов или условий в django? Или потенциальное решение?
Я не понимаю часть "...это приводит к рендерингу 1 блока и 14 обращениям к кэшу... Это, очевидно, гораздо менее производительно.".
Если все блоки, кроме одного, должны быть кэшированы, и количество вызовов / время обхода является проблемой (вы хотите выполнять только один поиск в кэше), вы могли бы хранить в кэше dict со всеми отрисованными блоками и заменить пересчитанный, но теперь у вас будет штраф за обновление кэша со всем набором блоков.
Я думаю, что это действительно индивидуальный случай, поэтому вам придется сделать это в коде представления, примерно так:
def _render_block(request, block, context):
context['block'] = block
return render(request, block_template, context=context)
def view(request, ...):
...
rendered_blocks = []
cached_blocks = cache.get(cache_key) or {}
blocks_updated = False
for block in blocks:
rendered_block = cached_blocks.get(block.key)
if not rendered_block or block.disable_cache:
rendered_block = _render_block(request, block, context={...})
cached_blocks[block.key] = rendered_block
blocks_updated = True
rendered_blocks.append(rendered_block)
if blocks_updated:
cache.set(cache_key, cached_blocks)
...
context['blocks'] = rendered_blocks
return render(request, template, context)
Если бы это действительно имело значение, я бы сравнил время отклика при 14-кратном обращении к кэшу (гораздо более простой код) с хранением в кэше / восстановлением из кэша больших кусков. (Это зависит от вероятности обновления блока)
Посмотрите adv-cache-tag
.
У него есть тип {% nocache %} {% endnocache %}
django block, который можно обернуть вокруг динамического контента.
Это работает очень хорошо, но нужно помнить, что когда вы выходите из текущего кэшированного блока таким образом, вы не имеете доступа к своим локальным переменным. Если вы, например, использовали это в операторе with, у вас не будет своего 'with' внутри блока nocache.
В остальном вы можете использовать весь тот же синтаксис, что и при кэшировании фрагментов шаблонов django.
Я бы установил его из erudit-django-adv-cache-tag. В оригинальном проекте есть ссылка на Django 3.x django.utils.http.urlquote
- разработчик не отвечает на запросы о притяжении.
В противном случае вы можете установить оригинал как приложение и обновить следующий импорт в tags.py из:
from django.utils.http import urlquote
до
from urllib.parse import quote
и обновить
class CacheTag(object, metaclass=CacheTagMetaClass):
....
def hash_args(self):
"""
Take all the arguments passed after the fragment name and return a
hashed version which will be used in the cache key
"""
return hashlib.md5(force_bytes(':'.join([quote(force_bytes(var)) for var in self.vary_on]))).hexdigest()