Django: Кэширование

Django 2008-04-13 Dovbush Pavel

В продолжение моей статьи про шаблоны, хочу рассказать про реализацию кэширования в Django. Основной упор будет сделан на кэширование частей шаблона – этот вопрос был затронут тут и послужил причиной написания этих двух статей. В предыдущей статье я слишком увлекся описанием самих шаблонов, так что постараюсь исправиться в этой.

Что такое кэширование, и какие оно дает преимущества под нагрузкой, думаю никому объяснять не надо. (Вступительные слова можно прочитать в начале соответствующего раздела документации по Django, ссылки в конце статьи.) Что можно кэшировать?

  • весь сайт;
  • конкретную страницу;
  • участок шаблона;
  • какие-то данные.

Остановимся подробнее на реализации этого в Django.

Подключение и настройка

Настройка механизма кэширования в Django очень проста. В файле настроек проекта нужно указать переменную CACHE_BACKEND содержащую параметры хранения кэша: где, как долго и сколько. Поддерживаются следующие типы хранилищ:

  • Memcached (в том числе на другой машине);
  • база данных;
  • файловая система;
  • локальная память.

Поддерживаются следующие параметры кэша:

  • timeout – время обновления кеша, в секундах;
  • max_entries – максимальное количество записей в кэше;
  • cull_frequency – процент старых записей, который удаляется по достижению max_entries.

Синтаксис описан в документации, и повторять его нет смысла.

Тем, у кого есть свои сервера или возможность выделит пару гигабайт памяти под кэш, думаю, эта статья будет малоинтересна. Поэтому хранение в Memcached и локальной памяти я описывать не буду. Выбор между БД и ФС предоставлю читателю. Насколько быстрее тот или иной способ я не сравнивал. Для себя я выбрал ФС т.к. БД и так нагружена.

Кроме того, для удобства разработки, поддерживается еще "dummy" хранилище, которое на самом деле ничего не хранит.

Отступление: production & development версии

Думаю, будет уместно описать способ безболезненного объединения этих двух версий.

Основным файлом конфигурации проекта Django является settings.py. Я добавил еще два файла: settings_dev.py и settings_pub.py, а в settings.py написал:

import platform
DEV_MODE=(platform.node()!='dpp.su')

if DEV_MODE:
	DEBUG = True
	from settings_dev import *
else:
	DEBUG = False
	from settings_pub import *

Функция platform.node возвращает имя компьютера, и я сравниваю его с именем своего сервера. Наверное, можно придумать более универсальное решение, но мне достаточно этого.

Таким образом, различающиеся настройки я выношу в эти файлы.

В нашем случае переменная CACHE_BACKEND в settings_dev.py будет равна 'dummy:///', а в settings_pub.py - 'file:///path/to/cache/'

Кэширование всего сайта

Для кэширования всего сайта достаточно добавить 'django.middleware.cache.CacheMiddleware' в список MIDDLEWARE_CLASSES и переменную CACHE_MIDDLEWARE_SECONDS.

Кэшируются все страницы без параметров.

Подробнее в документации.

Кэширование страницы (view)

Для кэширования страницы используется функция cache_page:

from django.views.decorators.cache import cache_page
def cache_this(request):
	...
cache_this = cache_page(cache_this, 60 * 15)

Также ее можно использовать в качестве декоратора:

@cache_page(60 * 15)
def cache_this(request):
	...

Параметр – время обновления кэша в секундах.

Кэширование данных

Для кэширования данных кэш Django предоставляет ожидаемые функции: set, get, delete и пару дополнительных. Настройки кэша берутся из переменной CACHE_BACKEND. Детали в документации.

 

Предыдущая часть статьи большей частью дублировала документацию Django – специально для того чтобы показать незнакомым с джангой, но знакомым с хабром, что кэширование в Django не требует от программиста никаких усилий.

 

Кэширование фрагмента шаблона

Кэширование частей шаблона мне кажется наиболее удобным для использования. Для динамичных сайтов (читай «в общем случае») кэшировать всю страницу нельзя. А в шаблоне мы кэшируем только нужный нам участок.

Для начала в шаблоне необходимо подключить библиотеку тегов кэширования.

{% load cache %}

Тэг cache принимает два обязательных параметра: время обновления кэша (в секундах) и имя кэшируемого фрагмента.

{% cache 500 sidebar %}
	.. sidebar ..
{% endcache %}

Кроме того можно передать сколько угодно дополнительных параметров для хранения нескольких версий кэша. Примерами могут быть разные версии кэша в зависимости от имени пользователя или от номера страницы при постраничной разбивке данных внутри блока.

{% cache 500 sidebar request.user.username %}
	.. sidebar for logged in user ..
{% endcache %}
{% cache 500 sidebar page %}
	.. content varies by page parameter ..
{% endcache %}

Попробую показать наглядно, что при этом происходит, и какие дает нам преимущества.

Возьмем пример из предыдущей статьи. Для шаблона blog/tag_index.htm будет построено следующее дерево (соответствие цветов см. в предыдущей статье):

Дерево блоков шаблона

Что тут можно кэшировать? В первую очередь нужно кэшировать редко изменяющиеся данные, доставаемые из базы.

Если мы даем пользователю возможность изменить информацию в «подвале» сайта стоит ее кэшировать – изменяется она очень редко.

{% block footer %}
	{% cache 5000 foot %}
		{% block copyright %}
		{% endblock %}
	{% endcache %}
{% endblock %}

Посмотрев внимательнее на примеры шаблонов в предыдущей статье можно увидеть, что вторая колонка (sidebar) не меняется для всех страниц блога. Поскольку для ее заполнения выполняется довольно много запросов (у меня – список тегов, количество статей для каждого тега, список последних статей) ее стоит кэшировать.

{% block sidebar %}
	{% cache 500 blog_sidebar %}
		{% block tags %}
		{% endblock %}
		{% block recent %}
		{% endblock %}
	{% endcache %}
{% endblock %}

Список статей тоже делает довольно много запросов: нужно выбрать статьи в зависимости от страницы и для каждой статьи выбрать соответствующие ей теги.

{% block content %}
	{{ tag.title }}
	{{ tag.text }}
	{% cache 500 article_list tag.id page %}
		{{ block.super }}
	{% endcache %}
{% endblock %}

Получим следующую картину:

Дерево блоков шаблона + кэширование

Что происходит при запросе этой страницы?

При построении дерева для каждого тега вызываются конструирующие его функции, предоставляющие данные для отрисовки. Именно в этот момент подготавливаются запросы в базу. Непосредственно сами запросы происходят при обращении к данным по причине их ленивой природы в Django. Затем парсер шаблонов обегает дерево и для каждого блока вызывает функцию отрисовки. В случае кэширующего тега вложенные в него теги будут сконструированы и отрисованы только если кэш еще не создан или устарел.

Если не использовать теги (лениться «плодить» теги для одного запроса в базу), то все данные необходимо предоставить самому шаблону. Да, по причине ленивой природы запросов в Django, разница не очень существенная. Однако получается не очень логично – контроллер вроде как отработал, а результатами его работы никто не воспользовался. Даже можно писать кучу питоновского кода в expr-тегах внутри самого шаблона – кэшу все равно. Однако подобные «нарушения» архитектуры намного сложнее поддерживать. А уж если это писал другой человек... работая в веб-студии и поддерживая кучу сайтов, я в этом убедился.

 

Теперь комментарий к статье, которая послужила поводом написания всего этого.

dmmd, фактически Вы предлагаете зачаточный вариант того, что реализовано в Django: теги и ленивые запросы в базу. Суть остается той же, однако архитектура Django кажется мне намного более стройной, очевидной и, соответственно легче поддерживаемой.

 

Ссылки на официальную документацию:

Перепечатка и обсуждение на хабре.