Система кэширования 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— путь к файлу сокета Memcached.
В этом примере Memcached работает на localhost (127.0.0.1) на порту 11211, используя привязку python-memcached.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
В этом примере Memcached доступен через локальный файл сокета /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 по адресам 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 не будет трогать существующую таблицу. Она будет создавать только отсутствующие таблицы.
Для вывода 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 имеет к ней доступ для чтения и записи.
Кэширование в локальной памяти
Если в файле настроек не указано другое, это является стандартным кэшем. Если вам нужны преимущества кэширования в памяти, но нет возможности запустить Memcached, рассмотрите локальный кэш в памяти. Этот кэш относится к одному процессу (см. ниже) и потокобезопасен. Для его использования установите BACKEND на "django.core.cache.backends.locmem.LocMemCache". Например:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
Кэш LOCATION используется для идентификации отдельных хранилищ памяти. Если у вас только один locmem кэш, вы можете опустить LOCATION; однако, если у вас более одного локального кэша памяти, вам необходимо назначить имя хотя бы одному из них, чтобы хранилища оставались разделенными.
Кэш использует стратегию удаления наименее используемых элементов (LRU).
Обратите внимание, что каждый процесс будет иметь свою собственную частную экземпляр кэша, что означает невозможность кэширования между процессами. Это очевидно также означает, что локальный кэш памяти не является особенно эффективным с точки зрения памяти, поэтому он, вероятно, не является хорошим выбором для производственных сред. Он удобен для разработки.
Старые версии используют псевдослучайную стратегию удаления, а не LRU.
Кэширование dummies (для разработки)
Наконец, Django поставляется с кэшем «dummy», который фактически не кэширует — он просто реализует интерфейс кэша, ничего не делая.
Это полезно, если у вас есть сайт в производстве, который использует высокопроизводительный кэширование в разных местах, но среда разработки/тестирования, где вы не хотите кэшировать и не хотите изменять свой код для специального случая последней. Чтобы активировать кэширование dummy, установите 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
}
}
}
Вот пример конфигурации для бэкенда, основанного на python-memcached, с ограничением размера объекта в 2 МБ:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'server_max_value_length': 1024 * 1024 * 2,
}
}
}
Вот пример конфигурации для бэкенда, основанного на 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,
}
}
}
}
Кэш на уровне сайта
После настройки кэша простейший способ использования кэширования — кэширование всего сайта. Вам нужно добавить 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в настройки MIDDLEWARE, как в этом примере:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
Примечание
Нет, это не опечатка: middleware «update» должен стоять первым в списке, а middleware «fetch» — последним. Подробности немного сложные, но см. Порядок MIDDLEWARE ниже, если вы хотите узнать подробности.
Затем добавьте следующие необходимые настройки в файл настроек 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:
- Устанавливает заголовок
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, если ваша структура 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, будут объединены.
Указание кэша на уровне представления в URLconf
Примеры в предыдущем разделе содержат жёстко запрограммированный факт кэширования представления, потому что cache_page изменяет функцию my_view на месте. Этот подход связывает ваше представление с системой кэширования, что не является идеальным по нескольким причинам. Например, вы можете захотеть повторно использовать функции представления на другом сайте без кэширования, или вы можете захотеть распределить представления по пользователям, которые могут использовать их без кэширования. Решение этих проблем заключается в указании кэша на уровне представления в URLconf, а не рядом с самими функциями представления.
Это делается легко: просто оберните функцию представления с помощью cache_page при упоминании её в URLconf. Вот старый URLconf из предыдущего примера:
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 %} кэширует содержимое блока в течение заданного времени. Он принимает как минимум два аргумента: время кэширования в секундах и имя фрагмента кэша. Фрагмент кэшируется постоянно, если время кэширования 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 %}
{% 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'].
Базовое использование
Базовый интерфейс:
-
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 в кэше, потому что вы не сможете отличить ваше сохранённое значение None от промаха кэша, обозначенного возвращаемым значением None.
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')
-
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.
Кэши нижнего уровня
До сих пор в этом документе рассматривалось кэширование своих данных. Но также существует другой тип кэширования, который важен для разработки веб-приложений: кэширование «нижнего уровня». Это системы, которые кэшируют страницы для пользователей даже до того, как запрос достигнет вашего веб-сайта.
Вот несколько примеров кэшей нижнего уровня:
- Ваш интернет-провайдер может кэшировать определенные страницы, поэтому если вы запросите страницу с https://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-адрес — например, "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['Vary'] = 'user-agent' ) заключается в том, что декоратор добавляет к заголовку Vary (который может уже существовать), а не задаёт его с нуля и потенциально не переопределяет всё, что уже там было.
Вы можете передать несколько заголовков в vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
...
Это сообщает кэшам ниже по уровню изменять значения в зависимости от обоих параметров, что означает, что каждая комбинация пользовательского агента и куки будет иметь своё значение кэша. Например, запрос с пользовательским агентом 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
Вы также можете контролировать кэши ниже по уровню другими способами (см. RFC 7234 для получения подробностей об HTTP-кэшировании). Например, даже если вы не используете фреймворк кэширования Django на стороне сервера, вы всё ещё можете указать клиентам кэшировать представление на определённый промежуток времени с помощью директивы max-age:
from django.views.decorators.cache import cache_control
@cache_control(max_age=3600)
def my_view(request):
...
(Если вы используете кэширующий посредник, он уже устанавливает max-age со значением параметра CACHE_MIDDLEWARE_SECONDS. В этом случае настройка max_age от декоратора cache_control() будет иметь приоритет, и значения заголовка будут объединены правильно.)
Любая допустимая директива Cache-Control ответа действительна в cache_control(). Вот несколько дополнительных примеров:
no_transform=Truemust_revalidate=Truestale_while_revalidate=num_seconds
Полный список известных директив можно найти в реестре IANA (обратите внимание, что не все они относятся к ответам).
Если вы хотите отключить кэширование с помощью заголовков, never_cache() — это декоратор представления, который добавляет заголовки, чтобы гарантировать, что браузеры или другие кэши не будут кэшировать ответ. Пример:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
...
Порядок MIDDLEWARE
Если вы используете кэширующий посредник, важно правильно разместить каждый его фрагмент в настройке MIDDLEWARE. Это потому, что кэширующему посреднику необходимо знать, какие заголовки следует использовать для изменения хранения кэша. Посредник всегда добавляет что-то в заголовок ответа Vary , когда может.
UpdateCacheMiddleware выполняется на фазе ответа, где посредники выполняются в обратном порядке, поэтому элемент в начале списка выполняется последним на фазе ответа. Таким образом, вам нужно убедиться, что UpdateCacheMiddleware находится перед любыми другими посредниками, которые могут добавить что-то в заголовок Vary . Следующие модули посредников делают это:
-
SessionMiddlewareдобавляетCookie -
GZipMiddlewareдобавляетAccept-Encoding -
LocaleMiddlewareдобавляетAccept-Language
FetchFromCacheMiddleware, с другой стороны, выполняется на фазе запроса, где посредники применяются от первого к последнему, поэтому элемент в начале списка выполняется первым на фазе запроса. FetchFromCacheMiddleware также должен выполняться после того, как другие посредники обновили заголовок Vary , поэтому FetchFromCacheMiddleware должен быть после любого элемента, который это делает.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.2/topics/cache/