Система кэширования Django
Основной компромисс при создании динамических веб-сайтов заключается в том, что они динамичны. Каждый раз, когда пользователь запрашивает страницу, веб-сервер выполняет различные вычисления — от запросов к базе данных до рендеринга шаблонов и бизнес-логики — для создания страницы, которую видит посетитель сайта. Это намного дороже с точки зрения накладных расходов на обработку, чем стандартная схема сервера, читающего файлы из файловой системы.
Для большинства веб-приложений эти накладные расходы не являются проблемой. Большинство веб-приложений не washingtonpost.com или slashdot.org; они просто небольшие или средние сайты со средним трафиком. Но для сайтов со средним или высоким трафиком крайне важно сократить все возможные накладные расходы.
Именно тут появляется кэширование.
Кэширование — это сохранение результата дорогостоящего вычисления, чтобы вам не приходилось его выполнять в следующий раз. Вот псевдокод, объясняющий, как это будет работать для динамически генерируемой веб-страницы:
given a URL, try finding that page in the cache
if the page is in the cache:
return the cached page
else:
generate the page
save the generated page in the cache (for next time)
return the generated page
Django поставляется с мощной системой кэширования, которая позволяет сохранять динамические страницы, чтобы не приходилось их рассчитывать для каждого запроса. Для удобства Django предлагает различные уровни гранулярности кэширования: вы можете кэшировать выходные данные определенных представлений, кэшировать только трудновычисляемые части или кэшировать весь сайт.
Django также хорошо работает с «потоками» кэширования, такими как Squid и кэши браузера. Это типы кэшей, которыми вы не управляете напрямую, но можете предоставлять подсказки (через HTTP-заголовки) о том, какие части вашего сайта следует кэшировать и как.
См. также
В философии проектирования системы кэширования описываются некоторые решения по проектированию.
Настройка кэширования
Система кэширования требует немного настройки. В частности, вам необходимо указать, где должны храниться ваши кэшированные данные — в базе данных, на файловой системе или непосредственно в памяти. Это важное решение, которое влияет на производительность кэша; да, некоторые типы кэшей быстрее других.
Предпочтение к типу кэша указывается в настройке CACHES файла настроек. Вот описание всех доступных значений для CACHES.
Memcached
Самый быстрый и эффективный тип кэша, поддерживаемый Django, Memcached — это полностью основанный на памяти кэш-сервер, первоначально разработанный для обработки больших нагрузок на LiveJournal.com и впоследствии выпущенный под открытой лицензией компанией Danga Interactive. Он используется такими сайтами, как Facebook и Wikipedia, для уменьшения доступа к базе данных и значительного повышения производительности сайта.
Memcached работает как демон и получает выделенный объём оперативной памяти. Всё, что он делает, — это предоставляет быстрый интерфейс для добавления, извлечения и удаления данных из кэша. Все данные хранятся непосредственно в памяти, поэтому нет накладных расходов на использование базы данных или файловой системы.
После установки самого Memcached вам нужно установить привязку Memcached. Существует несколько доступных Python-привязок Memcached; две наиболее распространённые — python-memcached и pylibmc.
Чтобы использовать Memcached с Django:
- Установите
BACKENDв значениеdjango.core.cache.backends.memcached.MemcachedCacheилиdjango.core.cache.backends.memcached.PyLibMCCache(в зависимости от выбранной привязки memcached) - Установите
LOCATIONв значенияip:port, гдеip— IP-адрес демона Memcached иport— порт, на котором работает Memcached, или в значениеunix:path, гдеpath— путь к файлу Unix-соккета Memcached.
В этом примере Memcached работает на локальном хосте (127.0.0.1) на порту 11211, используя привязку python-memcached.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
В этом примере Memcached доступен через локальный файл Unix-соккета /tmp/memcached.sock с использованием привязки python-memcached.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
При использовании привязки pylibmc не включайте префикс unix:/:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '/tmp/memcached.sock',
}
}
Отличной особенностью Memcached является его способность к совместному использованию кэша между несколькими серверами. Это означает, что вы можете запустить демоны Memcached на нескольких машинах, и программа будет рассматривать группу машин как единый кэш, без необходимости дублирования значений кэша на каждой машине. Чтобы воспользоваться этой функцией, включите все адреса серверов в LOCATION, разделенные точкой с запятой или в виде списка.
В этом примере кэш используется совместно между экземплярами Memcached, работающими по IP-адресам 172.19.26.240 и 172.19.26.242, оба на порту 11211:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11211',
]
}
}
В следующем примере кэш используется совместно между экземплярами Memcached, работающими по IP-адресам 172.19.26.240 (порт 11211), 172.19.26.242 (порт 11212) и 172.19.26.244 (порт 11213):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11212',
'172.19.26.244:11213',
]
}
}
Последний момент о Memcached: кэширование на основе памяти имеет недостаток: поскольку кэшированные данные хранятся в памяти, они будут потеряны при сбое сервера. Очевидно, что память не предназначена для постоянного хранения данных, поэтому не полагайтесь на кэширование на основе памяти как на единственное хранилище данных. Несомненно, ни один из кэш-бекендов Django не предназначен для постоянного хранения — все они предназначены для кэширования, а не хранения — но мы указываем это здесь, потому что кэширование на основе памяти особенно временное.
Кэширование в базе данных
Django может хранить кэшированные данные в вашей базе данных. Это лучше всего работает, если у вас есть быстрый и индексированный сервер базы данных.
Чтобы использовать таблицу базы данных в качестве бекенда кэша:
- Установите
BACKENDв значениеdjango.core.cache.backends.db.DatabaseCache - Установите
LOCATIONв значениеtablename, имя таблицы базы данных. Это имя может быть любым, если это допустимое имя таблицы, которое ещё не используется в вашей базе данных.
В этом примере имя таблицы кэша — my_cache_table:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
Создание таблицы кэша
Перед использованием кэша в базе данных необходимо создать таблицу кэша с помощью этой команды:
python manage.py createcachetable
Это создаёт таблицу в вашей базе данных в правильном формате, ожидаемом системой кэширования базы данных Django. Имя таблицы взято из LOCATION.
Если вы используете несколько кэшей базы данных, createcachetable создаёт одну таблицу для каждого кэша.
Если вы используете несколько баз данных, createcachetable учитывает метод allow_migrate() ваших маршрутизаторов баз данных (см. ниже).
Как и migrate, createcachetable не будет трогать существующую таблицу. Она создаст только отсутствующие таблицы.
До Django 1.7, createcachetable создавала одну таблицу за раз. Вам нужно было указать имя таблицы, которую вы хотите создать, и если вы использовали несколько баз данных, вам нужно было использовать опцию --database.
Несколько баз данных
Если вы используете кэширование в базе данных с несколькими базами данных, вам также потребуется настроить инструкции для маршрутизации таблицы кэша базы данных. Для целей маршрутизации таблица кэша базы данных отображается как модель с именем CacheEntry, в приложении с именем django_cache. Эта модель не появится в кэше моделей, но её подробности могут быть использованы для целей маршрутизации.
Например, следующий маршрутизатор направит все операции чтения из кэша в cache_replica, а все операции записи в cache_primary. Таблица кэша будет синхронизирована только с cache_primary:
class CacheRouter(object):
"""A router to control all database cache operations"""
def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
if model._meta.app_label == 'django_cache':
return 'cache_replica'
return None
def db_for_write(self, model, **hints):
"All cache write operations go to primary"
if model._meta.app_label == 'django_cache':
return 'cache_primary'
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
if app_label == 'django_cache':
return db == 'cache_primary'
return None
Если вы не укажете направления маршрутизации для модели кэша базы данных, бекенд кэша будет использовать базу данных default.
Конечно, если вы не используете бекенд кэша базы данных, вам не нужно беспокоиться о предоставлении инструкций по маршрутизации для модели кэша базы данных.
Кэширование на файловой системе
Файловый бекенд сериализует и хранит каждое значение кэша в виде отдельного файла. Для использования этого бекенда установите BACKEND в значение "django.core.cache.backends.filebased.FileBasedCache" и LOCATION в подходящую директорию. Например, для хранения кэшированных данных в /var/tmp/django_cache, используйте эту настройку:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
}
}
Если вы работаете в Windows, поместите букву диска в начало пути, как в этом примере:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'c:/foo/bar',
}
}
Путь к директории должен быть абсолютным — начинаться с корня файловой системы. Неважно, ставите ли вы слеш в конце настройки.
Убедитесь, что директория, на которую указывает эта настройка, существует и доступна для чтения и записи пользователем системы, под которым работает ваш веб-сервер. Продолжая пример выше, если ваш сервер работает как пользователь apache, убедитесь, что директория /var/tmp/django_cache существует и доступна для чтения и записи пользователю apache.
Кэширование в локальной памяти
Это кэш по умолчанию, если в вашем файле настроек не указан другой. Если вам нужны преимущества кэширования в оперативной памяти, но у вас нет возможности запустить Memcached, рассмотрите бекенд локального кэша в оперативной памяти. Этот кэш является внутрипроцессным (см. ниже) и потокобезопасным. Для его использования установите BACKEND в значение "django.core.cache.backends.locmem.LocMemCache". Например:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
Кэш LOCATION используется для идентификации отдельных хранилищ памяти. Если у вас только один locmem кэш, вы можете опустить LOCATION; однако, если у вас более одного локального кэша памяти, вам необходимо назначить имя по крайней мере одному из них, чтобы поддерживать их разделение.
Обратите внимание, что каждый процесс будет иметь свой собственный частный экземпляр кэша, что означает невозможность кросс-процессного кэширования. Это очевидно также означает, что локальный кэш памяти не является особенно эффективным с точки зрения использования памяти, поэтому, вероятно, он не является хорошим выбором для производственных сред. Он удобен для разработки.
Кэширование модели (для разработки)
Наконец, Django поставляется с «модельным» кэшем, который фактически не кэширует — он просто реализует интерфейс кэша, ничего не делая.
Это полезно, если у вас есть сайт производства, который использует высокопроизводительное кэширование в различных местах, но среда разработки/тестирования, где вы не хотите кэшировать и не хотите изменять свой код для специальных случаев последней. Для активации кэширования модели установите BACKEND следующим образом:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
Использование пользовательского кэша
Хотя Django включает поддержку ряда кэшей по умолчанию, иногда вам может потребоваться использовать настраиваемый кэш. Для использования внешнего кэша с Django используйте путь импорта Python в качестве BACKEND настройки CACHES, например:
CACHES = {
'default': {
'BACKEND': 'path.to.backend',
}
}
Если вы создаете свой собственный бэкенд, вы можете использовать стандартные кэши как эталонные реализации. Вы найдете код в каталоге django/core/cache/backends/ исходного кода Django.
Примечание. Без веской причины, такой как хост, который их не поддерживает, вы должны придерживаться кэшей, включенных в Django. Они хорошо протестированы и просты в использовании.
Аргументы кэша
Каждый кэш-бэкенд может получать дополнительные аргументы для управления поведением кэширования. Эти аргументы предоставляются в качестве дополнительных ключей в настройке CACHES. Допустимые аргументы следующие:
-
TIMEOUT: Время по умолчанию в секундах, используемое для кэша. По умолчанию этот аргумент равен300секундам (5 минут).Вы можете установить
TIMEOUTнаNone, чтобы по умолчанию ключи кэша никогда не истекали. Значение0заставляет ключи немедленно истекать (эффективно «не кэшировать»). -
OPTIONS: Любые параметры, которые должны быть переданы кэшу. Список допустимых параметров будет варьироваться в зависимости от каждого бэкенда, а бэкенды кэша, основанные на сторонней библиотеке, будут передавать свои параметры непосредственно в базовую библиотеку кэша.Кэширующие бэкенды, реализующие собственную стратегию удаления (т.е., бэкенды
locmem,filesystemиdatabase), будут учитывать следующие параметры:-
MAX_ENTRIES: Максимальное количество элементов, разрешенных в кэше, прежде чем старые значения будут удалены. По умолчанию этот аргумент равен300. -
CULL_FREQUENCY: Доля элементов, которые удаляются, когда достигнутоMAX_ENTRIES. Фактическое соотношение равно1 / CULL_FREQUENCY, поэтому установитеCULL_FREQUENCYна2, чтобы удалить половину элементов, когда достигнутоMAX_ENTRIES. Этот аргумент должен быть целым числом и по умолчанию равен3.Значение
0дляCULL_FREQUENCYозначает, что весь кэш будет сброшен, когдаMAX_ENTRIESбудет достигнуто. В некоторых бэкендах (в частности, вdatabase) это делает удаление намного быстрее за счет увеличения промахов кэша.
-
-
KEY_PREFIX: Строка, которая будет автоматически включена (по умолчанию в качестве префикса) ко всем ключам кэша, используемым сервером Django.Дополнительную информацию см. в разделе документация по кэшу.
-
VERSION: Номер версии по умолчанию для ключей кэша, генерируемых сервером Django.Дополнительную информацию см. в разделе документация по кэшу.
-
KEY_FUNCTION: Строка, содержащая путь с точками к функции, определяющей, как составить префикс, версию и ключ в окончательный ключ кэша.Дополнительную информацию см. в разделе документация по кэшу.
В этом примере кэширующий бэкенд файловой системы настраивается с временем ожидания 60 секунд и максимальной емкостью 1000 элементов:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
'TIMEOUT': 60,
'OPTIONS': {
'MAX_ENTRIES': 1000
}
}
}
Недопустимые аргументы игнорируются, как и недопустимые значения известных аргументов.
Кэш на сайт
После настройки кэша самый простой способ его использования — это кэширование всего сайта. Вам нужно добавить 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в настройку MIDDLEWARE_CLASSES, как показано в этом примере:
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
)
Примечание
Нет, это не опечатка: «middleware обновления» должен стоять первым в списке, а «middleware извлечения» — последним. Подробности немного сложны, но ознакомьтесь с Порядок MIDDLEWARE_CLASSES ниже, если хотите узнать все подробности.
Затем добавьте следующие необходимые настройки в файл настроек Django:
-
CACHE_MIDDLEWARE_ALIAS— псевдоним кэша для хранения. -
CACHE_MIDDLEWARE_SECONDS— количество секунд, в течение которого должна кэшироваться каждая страница. -
CACHE_MIDDLEWARE_KEY_PREFIX— если кэш используется на нескольких сайтах с использованием той же установки Django, установите это значение в имя сайта или другую уникальную для этого экземпляра Django строку для предотвращения коллизий ключей. Используйте пустую строку, если вам это не нужно.
FetchFromCacheMiddleware кэширует ответы GET и HEAD со статусом 200, где это разрешено заголовками запроса и ответа. Ответы на запросы одной и той же URL-адресы с различными параметрами запроса считаются уникальными страницами и кэшируются отдельно. Этот middleware ожидает, что запрос HEAD будет отвечать теми же заголовками ответа, что и соответствующий запрос GET; в этом случае он может вернуть кэшированный ответ GET для запроса HEAD.
Кроме того, UpdateCacheMiddleware автоматически устанавливает несколько заголовков в каждом HttpResponse:
- Устанавливает заголовок
Last-Modifiedв текущую дату/время при запросе новой (не кэшированной) версии страницы. - Устанавливает заголовок
Expiresв текущую дату/время плюс определенное значениеCACHE_MIDDLEWARE_SECONDS. - Устанавливает заголовок
Cache-Controlдля указания максимального возраста страницы — опять же, из настройкиCACHE_MIDDLEWARE_SECONDS.
Дополнительную информацию о middleware см. в разделе Middleware.
Если представление устанавливает собственное время истечения кэша (т.е. имеет раздел max-age в заголовке Cache-Control), то страница будет кэшироваться до истечения срока действия, а не до CACHE_MIDDLEWARE_SECONDS. Используя декораторы в django.views.decorators.cache, вы можете легко установить срок действия представления (с помощью декоратора cache_control) или отключить кэширование для представления (с помощью декоратора never_cache). Дополнительную информацию об использовании других заголовков см. в разделе использование других заголовков.
Если USE_I18N установлено в True, то сгенерированный ключ кэша будет включать имя активного языка — см. также Как Django определяет предпочтение языка). Это позволяет легко кэшировать многоязычные сайты, не создавая ключ кэша самостоятельно.
Ключи кэша также включают активный язык при установке USE_L10N в значение True и текущая зона времени при установке USE_TZ в значение True.
Кэширование на уровне представления
-
django.views.decorators.cache.cache_page()
Более тонкий способ использования системы кэширования — кэширование выходных данных отдельных представлений. django.views.decorators.cache определяет декоратор cache_page, который автоматически кэширует ответ представления для вас. Он прост в использовании:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def my_view(request):
...
cache_page принимает один аргумент: время кэширования в секундах. В приведенном выше примере результат представления my_view() будет кэшироваться в течение 15 минут. (Обратите внимание, что мы написали его как 60 * 15 для удобства чтения. 60 * 15 будет вычислено как 900 — т.е. 15 минут, умноженных на 60 секунд в минуте.)
Кэш на уровне представления, как и кэш на уровне сайта, использует URL в качестве ключа. Если несколько URL указывают на одно и то же представление, каждый URL будет кэшироваться отдельно. Продолжая пример my_view, если ваш URLconf выглядит так:
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', my_view),
]
то запросы к /foo/1/ и /foo/23/ будут кэшироваться отдельно, как ожидается. Но после запроса определенного URL (например, /foo/23/) последующие запросы к этому URL будут использовать кэш.
cache_page также может принимать необязательный ключевой аргумент cache, который указывает декоратору использовать определенный кэш (из вашего параметра CACHES) при кэшировании результатов представления. По умолчанию используется кэш default, но вы можете указать любой кэш:
@cache_page(60 * 15, cache="special_cache")
def my_view(request):
...
Вы также можете переопределить префикс кэша на уровне представления. cache_page принимает необязательный ключевой аргумент key_prefix, который работает так же, как и параметр CACHE_MIDDLEWARE_KEY_PREFIX для middleware. Его можно использовать так:
@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
...
Аргументы key_prefix и cache могут быть указаны вместе. Аргумент key_prefix и KEY_PREFIX, указанный в CACHES, будут объединены.
Указание кэша на уровне представления в URLconf
Примеры в предыдущем разделе жестко закодировали тот факт, что представление кэшируется, потому что cache_page изменяет функцию my_view на месте. Такой подход связывает ваше представление с системой кэширования, что нежелательно по нескольким причинам. Например, вы можете захотеть повторно использовать функции представления на другом, некэшируемом сайте, или вы можете захотеть распределить представления среди людей, которые могут захотеть использовать их без кэширования. Решение этих проблем заключается в указании кэша на уровне представления в URLconf, а не рядом с самими функциями представления.
Это легко сделать: просто оберните функцию представления с помощью cache_page при ее указании в URLconf. Вот старый URLconf из предыдущего примера:
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', my_view),
]
Вот то же самое, с my_view обернутой в cache_page:
from django.views.decorators.cache import cache_page
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]
Кэширование фрагментов шаблона
Если вам нужен еще больший контроль, вы также можете кэшировать фрагменты шаблонов, используя тег cache шаблона. Чтобы предоставить вашему шаблону доступ к этому тегу, поместите {% load cache %} в начало своего шаблона.
Тег {% cache %} шаблона кэширует содержимое блока на заданное время. Он принимает как минимум два аргумента: время ожидания кэша в секундах и имя фрагмента кэша. Имя используется как есть, не используйте переменную. Например:
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
Иногда вы можете захотеть кэшировать несколько копий фрагмента в зависимости от динамических данных, которые появляются внутри фрагмента. Например, вы можете захотеть отдельную кэшированную копию сайдбара, использованного в предыдущем примере, для каждого пользователя вашего сайта. Для этого передайте дополнительные аргументы в тег шаблона {% cache %} для уникальной идентификации фрагмента кэша:
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
Совершенно нормально указывать более одного аргумента для идентификации фрагмента. Просто передайте столько аргументов в {% cache %} сколько вам нужно.
Если USE_I18N установлено в True, кэш middleware на уровне сайта будет учитывать активный язык. Для тега шаблона cache вы можете использовать одну из переменных, специфичных для перевода, доступных в шаблонах, чтобы достичь того же результата:
{% load i18n %}
{% load cache %}
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% trans "Welcome to example.com" %}
{% endcache %}
Время ожидания кэша может быть переменной шаблона, при условии, что переменная шаблона резолвится в целое значение. Например, если переменная шаблона my_timeout установлена в значение 600, то следующие два примера эквивалентны:
{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}
Эта функция полезна для избежания повторений в шаблонах. Вы можете установить время ожидания в переменную в одном месте и просто повторно использовать это значение.
По умолчанию тег кэша будет пытаться использовать кэш с именем «template_fragments». Если такого кэша не существует, он будет использовать кэш по умолчанию. Вы можете выбрать альтернативный бэкэнд кэша, используя ключевой аргумент using, который должен быть последним аргументом тега.
{% cache 300 local-thing ... using="localcache" %}
Указание имени кэша, которого нет в конфигурации, считается ошибкой.
-
django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None)
Если вы хотите получить ключ кэша, используемый для кэшированного фрагмента, вы можете использовать make_template_fragment_key. fragment_name — это то же самое, что и второй аргумент тега шаблона cache; vary_on — это список всех дополнительных аргументов, переданных в тег. Эта функция может быть полезна для аннулирования или перезаписи кэшированного элемента, например:
>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
>>> key = make_template_fragment_key('sidebar', [username])
>>> cache.delete(key) # invalidates cached template fragment
API кэширования низкого уровня
Иногда кэширование всей отображаемой страницы не приносит большой пользы и, на самом деле, является излишним.
Например, возможно, ваш сайт содержит представление, результаты которого зависят от нескольких дорогостоящих запросов, результаты которых меняются с разной периодичностью. В этом случае использование кэширования всей страницы, которое предлагают стратегии кэширования на уровне сайта или на уровне представления, не было бы оптимальным, поскольку вам не нужно было бы кэшировать весь результат (поскольку некоторые данные часто меняются), но вам все равно нужно было бы кэшировать результаты, которые редко меняются.
Для таких случаев Django предоставляет простой API кэширования низкого уровня. Вы можете использовать этот API для хранения объектов в кэше с любой желаемой степенью детализации. Вы можете кэшировать любые объекты Python, которые могут быть безопасно сериализованы: строки, словари, списки объектов модели и так далее. (Большинство обычных объектов Python могут быть сериализованы; для получения дополнительной информации о сериализации см. документацию Python.)
Доступ к кэшу
-
django.core.cache.caches -
Вы можете получить доступ к кэшам, настроенным в параметре
CACHES, через объект, похожий на словарь:django.core.cache.caches. Повторные запросы к одному и тому же псевдониму в одном и том же потоке вернут один и тот же объект.>>> from django.core.cache import caches >>> cache1 = caches['myalias'] >>> cache2 = caches['myalias'] >>> cache1 is cache2 True
Если указанного ключа не существует, будет поднято исключение
InvalidCacheBackendError.Для обеспечения потокобезопасности для каждого потока будет возвращен другой экземпляр бэкэнда кэша.
-
django.core.cache.cache -
В качестве сокращения, кэш по умолчанию доступен как
django.core.cache.cache:>>> from django.core.cache import cache
Этот объект эквивалентен
caches['default'].
-
django.core.cache.get_cache(backend, **kwargs) -
Deprecated since version 1.7: Эта функция устарела и заменена на
caches.До Django 1.7 эта функция была каноническим способом получения экземпляра кэша. Ее также можно было использовать для создания нового экземпляра кэша с другой конфигурацией.
>>> from django.core.cache import get_cache >>> get_cache('default') >>> get_cache('django.core.cache.backends.memcached.MemcachedCache', LOCATION='127.0.0.2') >>> get_cache('default', TIMEOUT=300)
Основные примеры использования
Базовый интерфейс — это set(key, value, timeout) и get(key):
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'
Аргумент timeout необязателен и по умолчанию равен аргументу timeout соответствующего бэкэнда в настройке CACHES (описано выше). Это количество секунд, на которое значение должно храниться в кэше. Передача значения None для timeout будет кэшировать значение навсегда. А значение timeout 0 не будет кэшировать значение.
Если объект отсутствует в кэше, cache.get() возвращает None:
# Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
Не рекомендуется хранить буквальное значение None в кэше, так как вы не сможете отличить ваше сохраненное значение None от пропущенного кэша, обозначенного возвращаемым значением None.
cache.get() может принимать аргумент default. Это определяет значение, которое следует вернуть, если объект не существует в кэше:
>>> cache.get('my_key', 'has expired')
'has expired'
Чтобы добавить ключ только в том случае, если он еще не существует, используйте метод add(). Он принимает те же параметры, что и set(), но не попытается обновить кэш, если указанный ключ уже существует:
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
Если вам нужно узнать, сохранил ли add() значение в кэше, вы можете проверить возвращаемое значение. Оно будет True если значение было сохранено, False в противном случае.
Также есть интерфейс get_many(), который обращается к кэшу только один раз. get_many() возвращает словарь со всеми запрошенными ключами, которые фактически существуют в кэше (и не истекли):
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
Чтобы более эффективно установить несколько значений, используйте set_many() для передачи словаря пар ключ-значение:
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
Как и cache.set(), set_many() принимает необязательный параметр timeout.
Вы можете явно удалить ключи с помощью delete(). Это простой способ очистить кэш для определенного объекта:
>>> cache.delete('a')
Если вы хотите очистить сразу несколько ключей, delete_many() может принимать список ключей для удаления:
>>> cache.delete_many(['a', 'b', 'c'])
Наконец, если вы хотите удалить все ключи из кэша, используйте cache.clear(). Будьте осторожны, clear() удалит все из кэша, а не только ключи, установленные вашим приложением.
>>> cache.clear()
Вы также можете инкрементировать или декрементировать ключ, который уже существует, используя методы incr() или decr() соответственно. По умолчанию текущее значение кэша будет инкрементировано или декрементировано на 1. Другие значения инкремента/декремента могут быть указаны путем предоставления аргумента вызову инкремента/декремента. ValueError будет поднят, если вы попытаетесь инкрементировать или декрементировать несуществующий ключ кэша.:
>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6
Примечание
incr()/decr() методы не гарантируют атомарность. В тех бэкендах, которые поддерживают атомарное инкрементирование/декрементирование (в первую очередь, бэкенд memcached), операции инкрементирования и декрементирования будут атомарными. Однако, если бэкенд не предоставляет операцию инкрементирования/декрементирования напрямую, она будет реализована с помощью двухэтапной операции получения/обновления.
Вы можете закрыть соединение с кэшем с помощью close(), если это реализовано бэкендом кэша.
>>> cache.close()
Примечание
Для кэшей, которые не реализуют close методы, это является бесполезной операцией.
Префикс ключей кэша
Если вы используете экземпляр кэша на нескольких серверах или в разных средах (например, разработки и производства), данные, кэшированные одним сервером, могут быть использованы другим. Если формат кэшированных данных отличается между серверами, это может привести к труднодиагностируемым проблемам.
Для предотвращения этого Django предоставляет возможность добавлять префикс ко всем ключам кэша, используемым сервером. При сохранении или получении ключа кэша Django автоматически добавит префикс, используя значение параметра KEY_PREFIX кэша.
Обеспечив, что каждый экземпляр Django имеет уникальный префикс KEY_PREFIX, вы можете гарантировать отсутствие коллизий в кэшированных значениях.
Версии кэша
При изменении кода, использующего кэшированные значения, вам может потребоваться очистить все существующие кэшированные значения. Самый простой способ сделать это — очистить весь кэш, но это может привести к потере действительных и полезных значений кэша.
Django предоставляет более эффективный способ, позволяющий нацелиться на отдельные значения кэша. В рамках фреймворка кэша Django используется глобальный идентификатор версии, задаваемый с помощью параметра VERSION кэша. Значение этого параметра автоматически комбинируется с префиксом кэша и предоставленным пользователем ключом кэша для получения конечного ключа кэша.
По умолчанию любой запрос ключа автоматически включает версию ключа кэша по умолчанию. Однако все базовые функции кэша включают параметр version, позволяющий указать конкретную версию ключа кэша для установки или получения. Например:
# Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
# Get the default version (assuming version=1)
>>> cache.get('my_key')
None
# Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'
Версию конкретного ключа можно увеличить или уменьшить с помощью методов incr_version() и decr_version(). Это позволяет обновлять конкретные ключи до новой версии, не затрагивая другие. Продолжим наш предыдущий пример:
# Increment the version of 'my_key'
>>> cache.incr_version('my_key')
# The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
# But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'
Преобразование ключей кэша
Как описано в предыдущих двух разделах, ключ кэша, предоставленный пользователем, не используется напрямую — он комбинируется с префиксом кэша и версией ключа, чтобы получить окончательный ключ кэша. По умолчанию три части соединяются двоеточием для получения конечной строки:
def make_key(key, key_prefix, version):
return ':'.join([key_prefix, str(version), key])
Если вам необходимо объединить части другим способом или применить иную обработку к конечному ключу (например, вычисление хеш-значения из частей ключа), вы можете предоставить пользовательскую функцию для работы с ключами.
Параметр KEY_FUNCTION кэша задаёт путь к функции, соответствующей прототипу make_key() выше. Если эта функция указана, она будет использоваться вместо функции по умолчанию для объединения ключей.
Предупреждения по ключам кэша
Memcached, наиболее часто используемый бэкенд кэша в производственной среде, не допускает ключей кэша длиной более 250 символов, содержащих пробелы или управляющие символы, и использование таких ключей вызовет исключение. Чтобы поощрить использование переносимого кода для кэша и свести к минимуму неожиданные проблемы, другие встроенные бэкенды кэша выдают предупреждение (django.core.cache.backends.base.CacheKeyWarning) при использовании ключа, который вызовет ошибку в memcached.
Если вы используете бэкенд в производственной среде, который может принимать более широкий диапазон ключей (пользовательский бэкенд или один из встроенных бэкендов, не основанных на memcached), и хотите использовать этот более широкий диапазон без предупреждений, вы можете отключить CacheKeyWarning в модуле management одного из ваших приложений INSTALLED_APPS:
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)
Если вам нужно предоставить пользовательскую логику валидации ключей для одного из встроенных бэкендов, вы можете создать его подкласс, переопределить только метод validate_key и следовать инструкциям по использованию пользовательского бэкенда кэша. Например, для бэкенда locmem, поместите этот код в модуль:
from django.core.cache.backends.locmem import LocMemCache
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""Custom validation, raising exceptions or warnings as needed."""
# ...
…и используйте путь к этому классу в разделе BACKEND вашего параметра CACHES.
Кэши последующих уровней
До сих пор в этом документе упор делался на кэшировании собственных данных. Но для веб-разработки также актуален другой тип кэширования — кэширование, выполняемое «кэшами последующих уровней». Это системы, кэширующие страницы для пользователей ещё до получения запроса вашим веб-сайтом.
Вот несколько примеров кэшей последующих уровней:
- Ваш интернет-провайдер может кэшировать определённые страницы, поэтому если вы запросили страницу http://example.com/, ваш провайдер отправит вам эту страницу без прямого обращения к example.com. Администраторы example.com не знают об этом кэшировании; провайдер находится между example.com и вашим браузером, обрабатывая всё кэширование прозрачно.
- Ваш веб-сайт Django может находиться за прокси-кэшем, таким как Squid Web Proxy Cache (http://www.squid-cache.org/), который кэширует страницы для повышения производительности. В этом случае каждый запрос сначала обрабатывается прокси, и он передаётся вашему приложению только в случае необходимости.
- Ваш веб-браузер тоже кэширует страницы. Если веб-страница отправляет соответствующие заголовки, ваш браузер будет использовать локальную кэшированную копию для последующих запросов на эту страницу без повторного обращения к веб-странице, чтобы убедиться, что она не изменилась.
Кэширование последующих уровней — это хороший способ повышения эффективности, но существует и опасность: содержимое многих веб-страниц отличается в зависимости от авторизации и множества других переменных, а системы кэширования, которые слепо сохраняют страницы, основываясь только на URL-адресах, могут предоставить последующим посетителям некорректные или конфиденциальные данные.
Например, предположим, что вы управляете веб-системой электронной почты, и содержимое страницы «входящие» очевидно зависит от вошедшего пользователя. Если интернет-провайдер слепо кэширует ваш сайт, то первый пользователь, который вошёл через этого провайдера, получит кэшированную страницу «входящие» для последующих посетителей сайта. Это не хорошо.
К счастью, HTTP предоставляет решение этой проблемы. Существует ряд заголовков HTTP для указания кэшам последующих уровней, чтобы они различали содержимое кэша в зависимости от определённых переменных, и для указания механизмам кэширования не кэшировать определённые страницы. Мы рассмотрим некоторые из этих заголовков в следующих разделах.
Использование заголовков Vary
Заголовок Vary определяет, какие заголовки запроса механизм кэширования должен учитывать при создании своего ключа кэша. Например, если содержимое веб-страницы зависит от предпочтений языка пользователя, то страница считается «изменяющейся в зависимости от языка».
По умолчанию система кэша Django создаёт свои ключи кэша, используя полную квалифицированную URL-адрес запроса — например, "http://www.example.com/stories/2005/?order_by=author". Это означает, что каждый запрос на этот URL-адрес будет использовать ту же кэшированную версию, независимо от различий в пользовательском агенте, таких как файлы cookie или языковые предпочтения. Однако, если эта страница создаёт разное содержимое в зависимости от различий в заголовках запроса — таких как файл cookie, язык или пользовательский агент — вам потребуется использовать заголовок Vary , чтобы указать механизмам кэширования, что вывод страницы зависит от этих факторов.
Ключи кэша используют полную квалифицированную URL-адрес запроса, а не только путь и строку запроса.
Для этого в Django используйте удобный декоратор представления django.views.decorators.vary.vary_on_headers(), например так:
from django.views.decorators.vary import vary_on_headers
@vary_on_headers('User-Agent')
def my_view(request):
# ...
В этом случае механизм кэширования (такой как собственный кэш-мидлварь Django) будет кэшировать отдельные версии страницы для каждого уникального пользовательского агента.
Преимущество использования декоратора vary_on_headers вместо ручного задания заголовка Vary (используя что-то вроде response['Vary'] = 'user-agent') заключается в том, что декоратор добавляет к заголовку Vary (который может уже существовать), а не устанавливает его с нуля и потенциально переопределяет всё, что уже там было.
Вы можете передать несколько заголовков в vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
# ...
Это указывает кэшам последующих уровней на изменение в зависимости от обоих параметров, что означает, что каждый набор пользовательского агента и файла cookie получит своё значение кэша. Например, запрос с пользовательским агентом Mozilla и значением файла cookie foo=bar будет считаться отличным от запроса с пользовательским агентом Mozilla и значением файла cookie foo=ham.
Поскольку изменение в зависимости от cookie встречается очень часто, существует декоратор django.views.decorators.vary.vary_on_cookie(). Эти два представления эквивалентны:
@vary_on_cookie
def my_view(request):
# ...
@vary_on_headers('Cookie')
def my_view(request):
# ...
Заголовки, передаваемые в vary_on_headers, не чувствительны к регистру; "User-Agent" — то же самое, что и "user-agent".
Вы также можете использовать вспомогательную функцию django.utils.cache.patch_vary_headers() напрямую. Эта функция устанавливает или добавляет к Vary header. Например:
from django.utils.cache import patch_vary_headers
def my_view(request):
# ...
response = render_to_response('template_name', context)
patch_vary_headers(response, ['Cookie'])
return response
patch_vary_headers принимает экземпляр HttpResponse в качестве первого аргумента и список/кортеж имён заголовков, нечувствительных к регистру, в качестве второго аргумента.
Дополнительную информацию о заголовках Vary можно найти в официальной спецификации Vary.
Управление кэшем с помощью других заголовков
Другие проблемы с кэшированием связаны с конфиденциальностью данных и вопросом о том, где данные должны храниться в каскаде кэшей.
Пользователь обычно сталкивается с двумя типами кэшей: собственным кэшем браузера (частный кэш) и кэшем поставщика (публичный кэш). Публичный кэш используется многими пользователями и контролируется кем-то другим. Это создает проблемы с конфиденциальными данными — вы не хотите, чтобы, скажем, номер вашего банковского счета хранился в публичном кэше. Таким образом, веб-приложениям нужен способ сообщить кэшам, какие данные являются частными, а какие — публичными.
Решение заключается в указании, что кэш страницы должен быть «частным». Для этого в Django используйте декоратор представления cache_control. Пример:
from django.views.decorators.cache import cache_control
@cache_control(private=True)
def my_view(request):
# ...
Этот декоратор позаботится о отправке соответствующего HTTP-заголовка за кулисами.
Обратите внимание, что параметры управления кэшем «частный» и «публичный» являются взаимоисключающими. Декоратор гарантирует, что директива «публичный» удаляется, если нужно установить «частный» (и наоборот). Пример использования этих двух директив — сайт блога, который предлагает как частные, так и публичные записи. Публичные записи могут кэшироваться в любом общем кэше. Следующий код использует django.utils.cache.patch_cache_control(), ручной способ изменения заголовка управления кэшем (он вызывается внутри декоратором cache_control):
from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie
@vary_on_cookie
def list_blog_entries_view(request):
if request.user.is_anonymous():
response = render_only_public_entries()
patch_cache_control(response, public=True)
else:
response = render_private_and_public_entries(request.user)
patch_cache_control(response, private=True)
return response
Существует несколько других способов управления параметрами кэша. Например, HTTP позволяет приложениям делать следующее:
- Определить максимальное время кэширования страницы.
- Указать, должен ли кэш всегда проверять наличие новых версий, передавая кэшированное содержимое только в случае отсутствия изменений. (Некоторые кэши могут передавать кэшированное содержимое, даже если страница сервера изменилась, просто потому, что копия в кэше еще не истекла.)
В Django используйте декоратор представления cache_control для указания этих параметров кэша. В этом примере cache_control сообщает кэшам о необходимости повторной проверки кэша при каждом обращении и о хранении кэшированных версий не более чем на 3600 секунд:
from django.views.decorators.cache import cache_control
@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
# ...
Любая допустимая Cache-Control HTTP-директива допустима в cache_control(). Вот полный список:
public=Trueprivate=Trueno_cache=Trueno_transform=Truemust_revalidate=Trueproxy_revalidate=Truemax_age=num_secondss_maxage=num_seconds
Для получения объяснений HTTP-директив Cache-Control см. спецификацию Cache-Control.
(Обратите внимание, что middleware кэширования уже устанавливает максимальное значение заголовка кэша max-age со значением параметра CACHE_MIDDLEWARE_SECONDS. Если вы используете пользовательский max_age в декораторе cache_control, декоратор будет иметь приоритет, и значения заголовков будут объединены правильно.)
Если вы хотите использовать заголовки для отключения кэширования полностью, django.views.decorators.cache.never_cache — это декоратор представления, который добавляет заголовки для того, чтобы обеспечить, что ответ не будет кэшироваться браузерами или другими кэшами. Пример:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
# ...
Порядок MIDDLEWARE_CLASSES
Если вы используете middleware кэширования, важно правильно разместить каждый компонент в настройке MIDDLEWARE_CLASSES. Это связано с тем, что middleware кэша должен знать, какие заголовки использовать для изменения хранения кэша. Middleware всегда добавляет что-то в заголовок ответа Vary когда это возможно.
UpdateCacheMiddleware выполняется во время фазы ответа, где middleware выполняется в обратном порядке, поэтому элемент в начале списка выполняется последним во время фазы ответа. Таким образом, необходимо убедиться, что UpdateCacheMiddleware находится перед любым другим middleware, который может добавить что-то в заголовок Vary. Такие модули middleware:
-
SessionMiddlewareдобавляетCookie -
GZipMiddlewareдобавляетAccept-Encoding -
LocaleMiddlewareдобавляетAccept-Language
FetchFromCacheMiddleware, с другой стороны, выполняется во время фазы запроса, где middleware применяются от первого к последнему, поэтому элемент в начале списка выполняется первым во время фазы запроса. FetchFromCacheMiddleware также необходимо выполнить после того, как другой middleware обновит заголовок Vary, поэтому FetchFromCacheMiddleware должен находиться после любого элемента, который это делает.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/cache/