Система кэширования 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 вам потребуется установить его привязку. Существует несколько доступных Python-привязок к Memcached; две поддерживаемые Django — pylibmc и pymemcache.
Для использования Memcached с Django:
- Установите
BACKENDнаdjango.core.cache.backends.memcached.PyMemcacheCacheилиdjango.core.cache.backends.memcached.PyLibMCCache(в зависимости от выбранной вами привязки к memcached) - Установите
LOCATIONнаip:portзначения, гдеip— IP-адрес демона Memcached, аport— порт, на котором работает Memcached, или на значениеunix:path, гдеpath— путь к файлу сокета Memcached.
В этом примере Memcached работает на локальном хосте (127.0.0.1) на порту 11211, используя привязку pymemcache:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
В этом примере Memcached доступен через локальный файл сокета /tmp/memcached.sock с использованием привязки pymemcache:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': 'unix:/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.PyMemcacheCache',
'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.PyMemcacheCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11212',
'172.19.26.244:11213',
]
}
}
Окончательная особенность Memcached заключается в том, что кэширование в памяти имеет недостаток: поскольку кэшированные данные хранятся в памяти, они будут потеряны, если ваш сервер аварийно завершит работу. Очевидно, что память не предназначена для постоянного хранения данных, поэтому не полагайтесь на кэширование в памяти как на единственное хранилище данных. Несомненно, ни один из кэширующих бэкендов Django не предназначен для постоянного хранения — все они предназначены для кэширования, а не хранения, — но мы указываем это здесь, потому что кэширование в памяти является особенно временным.
Бэкенд PyMemcacheCache был добавлен.
Устарело начиная с версии 3.2: Бэкенд MemcachedCache устарел, так как python-memcached имеет некоторые проблемы и, похоже, не поддерживается. Используйте PyMemcacheCache или PyLibMCCache вместо него.
Кэширование в базе данных
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 не будет трогать существующую таблицу. Он создаст только отсутствующие таблицы.
Чтобы вывести SQL-запрос, вместо его выполнения, используйте опцию createcachetable --dry-run.
Несколько баз данных
Если вы используете кэширование в базе данных с несколькими базами данных, вам также потребуется настроить маршрутизацию для таблицы кэша базы данных. Для целей маршрутизации таблица кэша базы данных отображается как модель с именем CacheEntry, в приложении с именем django_cache. Эта модель не будет отображаться в кэше моделей, но её данные могут использоваться для целей маршрутизации.
Например, следующий роутер направит все операции чтения кэша в cache_replica, а все операции записи в cache_primary. Таблица кэша будет синхронизирована только с cache_primary:
class CacheRouter:
"""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, или что он может быть создан пользователем apache.
Предупреждение
Когда кеш LOCATION находится внутри MEDIA_ROOT, STATIC_ROOT или STATICFILES_FINDERS, конфиденциальные данные могут быть раскрыты.
Злоумышленник, получивший доступ к файлу кеша, может не только подделать содержимое HTML, которому ваш сайт будет доверять, но также удалённо выполнить произвольный код, поскольку данные сериализуются с помощью pickle.
Кэширование в оперативной памяти
Это кеш по умолчанию, если другой не указан в вашем файле настроек. Если вам нужны преимущества скорости кэширования в оперативной памяти, но у вас нет возможности использовать Memcached, рассмотрите кэш-бекенд локальной памяти. Этот кеш предназначен для каждого процесса (см. ниже) и потокобезопасен. Чтобы использовать его, установите BACKEND на "django.core.cache.backends.locmem.LocMemCache". Например:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
Кеш LOCATION используется для идентификации отдельных хранилищ памяти. Если у вас только один locmem кеш, вы можете опустить LOCATION; однако, если у вас более одного кэша локальной памяти, вам нужно назначить имя хотя бы одному из них, чтобы они оставались отдельными.
Кеш использует стратегию удаления наименее часто используемых элементов (LRU).
Обратите внимание, что каждый процесс будет иметь свою собственную частную экземпляр кеша, что означает, что совместное использование кеша между процессами невозможно. Это также означает, что кэш локальной памяти не является особенно эффективным с точки зрения использования памяти, поэтому, вероятно, он не является хорошим выбором для производственных сред. Он подходит для разработки.
Кэширование пустышек (для разработки)
Наконец, 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в частности) это делает удаление значительно быстрее за счёт большего количества пропусков кэша.
Бекенды Memcached передают содержимое
OPTIONSв качестве ключевых аргументов конструкторам клиента, позволяя более точно управлять поведением клиента. Пример использования см. ниже. -
-
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
}
}
}
Вот пример конфигурации бекенда на основе pylibmc, который включает двоичный протокол, аутентификацию SASL и режим работы ketama:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'binary': True,
'username': 'user',
'password': 'pass',
'behaviors': {
'ketama': True,
}
}
}
}
Вот пример конфигурации бекенда на основе pymemcache, который включает пул клиентов (что может повысить производительность, сохраняя подключённые клиенты), обрабатывает ошибки memcache/сети как пропуски кэша и устанавливает флаг TCP_NODELAY на сокете соединения:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'no_delay': True,
'ignore_exc': True,
'max_pool_size': 4,
'use_pooling': True,
}
}
}
Кеш для каждого сайта
После настройки кэша самый простой способ использования кэширования — кэширование всего сайта. Вам нужно добавить 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в настройки MIDDLEWARE, как в этом примере:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
Примечание
Нет, это не опечатка: миддлварь «update» должна стоять первой в списке, а миддлварь «fetch» — последней. Подробности немного неясные, но см. Порядок MIDDLEWARE ниже, если вам нужна полная история.
Затем добавьте следующие необходимые настройки в файл настроек Django:
-
CACHE_MIDDLEWARE_ALIAS— псевдоним кэша для хранения. -
CACHE_MIDDLEWARE_SECONDS— количество секунд, в течение которого каждая страница должна храниться в кэше. -
CACHE_MIDDLEWARE_KEY_PREFIX— если кэш используется на нескольких сайтах с использованием одной и той же установки Django, установите это значение в имя сайта или какую-либо другую строку, уникальную для этого экземпляра Django, чтобы предотвратить столкновения ключей. Используйте пустую строку, если вам это не нужно.
FetchFromCacheMiddleware кэширует ответы GET и HEAD со статусом 200, где заголовки запроса и ответа позволяют это. Ответы на запросы для одного и того же URL с различными параметрами запроса считаются уникальными страницами и кэшируются отдельно. Данный миддлварь ожидает, что запрос HEAD будет отвечен теми же заголовками ответа, что и соответствующий запрос GET; в этом случае он может вернуть кэшированный ответ GET для запроса HEAD.
Кроме того, UpdateCacheMiddleware автоматически устанавливает несколько заголовков в каждом HttpResponse, которые влияют на кеши ниже по потоку:
- Устанавливает заголовок
Expiresна текущую дату/время плюс определённое значениеCACHE_MIDDLEWARE_SECONDS. - Устанавливает заголовок
Cache-Controlдля задания максимального срока хранения страницы — опять же, из настройкиCACHE_MIDDLEWARE_SECONDS.
См. Миддлварь для получения дополнительной информации о миддлваре.
Если представление устанавливает свой срок действия кэша (т.е. у него есть раздел max-age в его Cache-Control заголовке), тогда страница будет кэшироваться до срока действия, а не до CACHE_MIDDLEWARE_SECONDS. Используя декораторы в django.views.decorators.cache вы можете легко установить срок действия представления (используя декоратор cache_control()) или отключить кэширование для представления (используя декоратор never_cache()). См. раздел использование других заголовков для получения дополнительной информации об этих декораторах.
Если USE_I18N установлено в True, то сгенерированный ключ кэша будет включать имя активного языка – см. также Как Django определяет предпочтительный язык). Это позволяет легко кэшировать многоязычные сайты, не создавая ключ кэша вручную.
Ключи кэша также включают текущую часовую зону когда 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 секунд в минуте.)
Время кэширования, заданное cache_page, имеет приоритет над директивой max-age из заголовка Cache-Control.
Кэш на уровне представления, как и кэш на уровне сайта, основывается на URL. Если несколько URL указывают на одно и то же представление, каждый URL будет кэшироваться отдельно. Продолжая пример my_view, если ваша URL-конфигурация выглядит так:
urlpatterns = [
path('foo/<int:code>/', 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, будут конкатенированы.
Кроме того, cache_page автоматически устанавливает заголовки Cache-Control и Expires в ответе, которые влияют на кэши последующих уровней.
В более старых версиях директива max-age из заголовка Cache-Control имела приоритет над временем кэширования, установленным cache_page.
Указание кэша на уровне представления в URLconf
Примеры в предыдущем разделе имеют жёстко заданный факт кэширования представления, потому что cache_page изменяет функцию my_view непосредственно. Такой подход связывает ваше представление с системой кэширования, что не является идеальным по нескольким причинам. Например, вы можете захотеть повторно использовать функции представления на другом сайте без кэширования, или вы можете захотеть распределить представления людям, которые могут их использовать без кэширования. Решением этих проблем является указание кэша на уровне представления в URLconf, а не рядом с самими функциями представления.
Вы можете сделать это, обернув функцию представления с помощью cache_page при ссылке на неё в URLconf. Вот старая URL-конфигурация из предыдущего примера:
urlpatterns = [
path('foo/<int:code>/', my_view),
]
Вот то же самое, но с my_view обернутым в cache_page:
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]
Кэширование фрагментов шаблона
Если вам нужен ещё больший контроль, вы также можете кэшировать фрагменты шаблона, используя тег cache шаблона. Чтобы предоставить шаблону доступ к этому тегу, поместите {% load cache %} в начало вашего шаблона.
Тег {% cache %} кэширует содержимое блока в течение заданного времени. Он принимает как минимум два аргумента: время кэширования в секундах и имя фрагмента кэша. Фрагмент кэшируется навсегда, если timeout равен None. Имя используется как есть, не используйте переменные. Например:
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
Иногда вы можете захотеть кэшировать несколько копий фрагмента в зависимости от динамических данных, которые появляются внутри фрагмента. Например, вы можете захотеть отдельную кэшированную копию сайдбара из предыдущего примера для каждого пользователя вашего сайта. Для этого передайте один или несколько дополнительных аргументов, которые могут быть переменными с фильтрами или без, в тег {% cache %} шаблона, чтобы уникально идентифицировать фрагмент кэша:
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
Если USE_I18N установлено в True, кэш middleware на уровне сайта будет учитывать активный язык. Для тега cache шаблона вы можете использовать одну из специфичных для перевода переменных, доступных в шаблонах, чтобы добиться того же результата:
{% load i18n %}
{% load cache %}
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% translate "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
True
API кэширования низкого уровня
Иногда кэширование всей отрисованной страницы не приносит существенной выгоды и, фактически, является излишним.
Например, возможно ваш сайт включает представление, результаты которого зависят от нескольких дорогостоящих запросов, результаты которых меняются с различными интервалами. В этом случае не было бы идеально использовать кэширование всей страницы, которое предлагают стратегии кэширования на уровне сайта или представления, потому что вы не хотели бы кэшировать весь результат (поскольку некоторые данные часто меняются), но вы все равно хотели бы кэшировать результаты, которые редко изменяются.
Для таких случаев Django предоставляет API кэширования низкого уровня. Вы можете использовать этот API для хранения объектов в кэше с любым уровнем детализации, который вам нужен. Вы можете кэшировать любой объект Python, который можно безопасно сохранить с помощью pickle: строки, словари, списки объектов модели и так далее. (Большинство распространённых объектов Python могут быть сохранены с помощью pickle; см. документацию Python для получения дополнительной информации о pickle.)
Доступ к кэшу
-
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'].
Базовое использование
Базовый интерфейс:
-
cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None) -
>>> cache.set('my_key', 'hello, world!', 30)
-
cache.get(key, default=None, version=None) -
>>> cache.get('my_key') 'hello, world!'
key должен быть str, а value может быть любым сохраняемым объектом Python.
Аргумент timeout является необязательным и по умолчанию равен аргументу timeout соответствующего бэкенда в настройке CACHES (объяснено выше). Это количество секунд, в течение которого значение должно храниться в кэше. Передача None для timeout будет кэшировать значение навсегда. timeout 0 не будет кэшировать значение.
Если объект отсутствует в кэше, cache.get() возвращает None:
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
Если вам нужно определить, существует ли объект в кэше, и вы сохранили литеральное значение None, используйте сенсорный объект в качестве значения по умолчанию:
>>> sentinel = object()
>>> cache.get('my_key', sentinel) is sentinel
False
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key', sentinel) is sentinel
True
MemcachedCache
Из-за ограничения python-memcached, невозможно отличить сохраненное None значение от пропущенного кэша, обозначенного возвращаемым значением None в устаревшем бэкенде MemcachedCache.
cache.get() может принимать аргумент default. Это указывает, какое значение вернуть, если объект не существует в кэше:
>>> cache.get('my_key', 'has expired')
'has expired'
-
cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)
Чтобы добавить ключ только в том случае, если он еще не существует, используйте метод add(). Он принимает те же параметры, что и set(), но не будет пытаться обновить кэш, если указанный ключ уже присутствует:
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
Если вам нужно узнать, сохранил ли add() значение в кэше, вы можете проверить возвращаемое значение. Оно вернет True, если значение было сохранено, False в противном случае.
-
cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT, version=None)
Если вы хотите получить значение ключа или установить значение, если ключ отсутствует в кэше, существует метод get_or_set(). Он принимает те же параметры, что и get(), но значение по умолчанию устанавливается как новое значение кэша для данного ключа, а не возвращается:
>>> cache.get('my_new_key') # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'
Вы также можете передать любой вызываемый объект как значение по умолчанию:
>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
-
cache.get_many(keys, version=None)
Также существует интерфейс 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}
-
cache.set_many(dict, timeout)
Для более эффективного задания нескольких значений используйте 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.
В поддерживаемых бэкендах (memcached) set_many() возвращает список ключей, которые не удалось вставить.
-
cache.delete(key, version=None)
Вы можете явно удалить ключи с помощью delete() для очистки кэша для определенного объекта:
>>> cache.delete('a')
True
delete() возвращает True, если ключ был успешно удален, False в противном случае.
Добавлено булево возвращаемое значение.
-
cache.delete_many(keys, version=None)
Если вы хотите очистить сразу несколько ключей, delete_many() может принимать список ключей для очистки:
>>> cache.delete_many(['a', 'b', 'c'])
-
cache.clear()
Наконец, если вы хотите удалить все ключи в кэше, используйте cache.clear(). Будьте осторожны, clear() удалит все из кэша, а не только ключи, заданные вашим приложением.
>>> cache.clear()
-
cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)
cache.touch() устанавливает новую дату истечения срока действия ключа. Например, чтобы обновить ключ, чтобы он истек через 10 секунд:
>>> cache.touch('a', 10)
True
Как и другие методы, аргумент timeout является необязательным и по умолчанию равен значению параметра TIMEOUT соответствующего бэкенда в настройке CACHES.
touch() возвращает True, если ключ был успешно обновлен, False в противном случае.
-
cache.incr(key, delta=1, version=None)
-
cache.decr(key, delta=1, version=None)
Вы также можете увеличивать или уменьшать ключ, который уже существует, используя методы 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), операции увеличения и уменьшения будут атомарными. Однако, если бэкенд не предоставляет операцию увеличения/уменьшения напрямую, она будет реализована с помощью двухэтапного извлечения/обновления.
-
cache.close()
Вы можете закрыть подключение к кэшу с помощью 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 '%s:%s:%s' % (key_prefix, 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.
Бэкенды кэша нижнего уровня
До сих пор в этом документе рассматривалось кэширование собственных данных. Но для веб-разработки также важен другой тип кэширования: кэширование, выполняемое «потоком вниз» (downstream) кэшами. Это системы, которые кэшируют страницы для пользователей еще до того, как запрос достигнет вашего веб-сайта.
Вот несколько примеров downstream-кэшей:
- Ваш интернет-провайдер (ISP) может кэшировать определенные страницы, поэтому, если вы запросили страницу с https://example.com/, ваш ISP отправит вам эту страницу, не обращаясь напрямую к example.com. Удаленный хостинг example.com не знает об этом кэшировании; ISP находится между example.com и вашим веб-браузером, обрабатывая всё кэширование прозрачно.
- Ваш веб-сайт на Django может находиться за прокси-кэшем, таким как Squid Web Proxy Cache (http://www.squid-cache.org/), который кэширует страницы для повышения производительности. В этом случае каждый запрос сначала обрабатывается прокси, и он передаётся вашему приложению только в случае необходимости.
- Ваш веб-браузер также кэширует страницы. Если веб-страница отправляет соответствующие заголовки, ваш браузер будет использовать локальную кэшированную копию для последующих запросов к этой странице, даже не обращаясь к веб-странице, чтобы проверить, не изменилась ли она.
Кэширование по потоку вниз повышает эффективность, но существует опасность: многие веб-страницы содержат различное содержимое в зависимости от аутентификации и ряда других переменных, и системы кэширования, слепо сохраняющие страницы только на основе URL-адресов, могут предоставить последующим посетителям неправильные или конфиденциальные данные этих страниц.
Например, если вы работаете с веб-системой электронной почты, содержимое страницы «входящие» зависит от пользователя, который вошел в систему. Если ваш ISP слепо кэширует ваш сайт, то первый пользователь, вошедший в систему через этого ISP, получит кэшированную страницу входящих сообщений для последующих посетителей сайта. Это нехорошо.
К счастью, HTTP предлагает решение этой проблемы. Существует ряд заголовков HTTP, которые предписывают downstream-кэшам изменять содержимое кэша в зависимости от указанных переменных и сообщать механизмам кэширования о том, какие страницы не следует кэшировать. В следующих разделах мы рассмотрим некоторые из этих заголовков.
Использование заголовков Vary
Заголовок Vary определяет, какие заголовки запроса механизм кэширования должен учитывать при построении ключа кэша. Например, если содержимое веб-страницы зависит от предпочтения языка пользователя, говорят, что страница «варируется по языку».
По умолчанию система кэширования Django создаёт ключи кэша, используя запрашиваемый полный URL-адрес, например "https://www.example.com/stories/2005/?order_by=author". Это означает, что каждый запрос к этому URL-адресу будет использовать ту же кэшированную версию, независимо от различий в пользовательском агенте, таких как куки или языковые предпочтения. Однако, если эта страница генерирует различное содержимое на основе некоторых различий в заголовках запроса, например, куки, языка или пользовательского агента, вам нужно использовать заголовок Vary, чтобы сообщить механизмам кэширования, что вывод страницы зависит от этих факторов.
Для этого в 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.headers['Vary'] =
'user-agent') заключается в том, что декоратор добавляет к заголовку Vary (который может уже существовать), а не устанавливает его с нуля и не перезаписывает потенциально уже существующую информацию.
Вы можете передать несколько заголовков в vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
...
Это указывает downstream-кэшам на вариативность по обоим параметрам, что означает, что каждой комбинации пользовательского агента и куки будет назначено собственное значение кэша. Например, запрос с пользовательским агентом Mozilla и значением куки foo=bar будет считаться отличным от запроса с пользовательским агентом Mozilla и значением куки foo=ham.
Поскольку вариативность по куки так распространена, существует декоратор 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.shortcuts import render
from django.utils.cache import patch_vary_headers
def my_view(request):
...
response = render(request, '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 за кулисами.
Обратите внимание, что настройки управления кэшем «частный» и «общедоступный» являются взаимоисключающими. Декоратор гарантирует, что директива «общедоступный» будет удалена, если нужно установить «частный» (и наоборот). Пример использования этих двух директив — веб-сайт блога, который предоставляет как частные, так и общедоступные записи. Общедоступные записи могут быть кэшированы в любом общем кэше. Следующий код использует 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
Вы также можете управлять downstream-кэшами другими способами (см. RFC 7234 для получения подробной информации о кэшировании HTTP). Например, даже если вы не используете серверную фреймворк для кэширования Django, вы можете указать клиентам кэшировать представление на определённое время с помощью директивы max-age:
from django.views.decorators.cache import cache_control
@cache_control(max_age=3600)
def my_view(request):
...
(Если вы используете кэширование middleware, оно уже задаёт заголовок max-age со значением настройки CACHE_MIDDLEWARE_SECONDS. В этом случае пользовательский заголовок max_age из декоратора cache_control() будет иметь приоритет, и значения заголовков будут объединены правильно.)
Любая действительная директива ответа Cache-Control допустима в cache_control(). Вот несколько дополнительных примеров:
no_transform=Truemust_revalidate=Truestale_while_revalidate=num_secondsno_cache=True
Полный список известных директив можно найти в реестре IANA (обратите внимание, что не все из них применяются к ответам).
Если вы хотите полностью отключить кэширование с помощью заголовков, never_cache() — декоратор представления, который добавляет заголовки, чтобы гарантировать, что ответ не будет кэширован браузерами или другими кэшами. Пример:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
...
Порядок MIDDLEWARE
Если вы используете кэширование middleware, важно правильно разместить каждый компонент в настройке MIDDLEWARE. Это связано с тем, что 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/3.2/topics/cache/