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 кажется мне намного более стройной, очевидной и, соответственно легче поддерживаемой.
Ссылки на официальную документацию:
- djangodocs: Django’s cache framework
- djangobook: Django’s cache framework
- djangobook (перевод): Кэширование
Перепечатка и обсуждение на хабре.