Условное кэширование при просмотре блоков в 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()
Вернуться на верх