Система кэширования 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, работающими на IP-адресах 172.19.26.240 и 172.19.26.242, оба на порту 11211.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11211',
]
}
}
В следующем примере кэш совместно используется экземплярами Memcached, работающими на IP-адресах 172.19.26.240 (порт 11211), 172.19.26.242 (порт 11212) и 172.19.26.244 (порт 11213).
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11212',
'172.19.26.244:11213',
]
}
}
Последнее замечание о Memcached: кэширование на основе памяти имеет недостаток: поскольку кэшированные данные хранятся в памяти, данные будут потеряны при сбое сервера. Очевидно, память не предназначена для постоянного хранения данных, поэтому не полагайтесь на кэширование на основе памяти как на единственное хранилище данных. Без сомнения, ни один из кэш-бэкендов Django не должен использоваться для постоянного хранения — все они предназначены для кэширования, а не хранения — но мы указываем на это здесь, потому что кэширование на основе памяти особенно временное.
Кэширование в базе данных
Django может хранить кэшированные данные в вашей базе данных. Это лучше всего работает, если у вас есть быстрый сервер базы данных с хорошей индексацией.
Для использования таблицы базы данных как кэш-бэкенда:
- Установите
BACKENDнаdjango.core.cache.backends.db.DatabaseCache - Установите
LOCATIONнаtablename, имя таблицы базы данных. Это имя может быть любым, если это допустимое имя таблицы, которое еще не используется в вашей базе данных.
В этом примере имя таблицы кэша — my_cache_table:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
Создание таблицы кэша
Прежде чем использовать кэш в базе данных, вы должны создать таблицу кэша с помощью этой команды:
python manage.py createcachetable
Это создает таблицу в вашей базе данных в правильном формате, ожидаемом системой кэширования базы данных Django. Название таблицы взято из LOCATION.
Если вы используете несколько кэшей в базе данных, createcachetable создает одну таблицу для каждого кэша.
Если вы используете несколько баз данных, createcachetable учитывает метод allow_migrate() ваших роутеров баз данных (см. ниже).
Как и migrate, createcachetable не будет трогать существующую таблицу. Он будет создавать только отсутствующие таблицы.
Чтобы вывести 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.
Кэширование в локальной памяти
Это кэш по умолчанию, если другой не указан в вашем файле настроек. Если вы хотите получить преимущества кэширования в памяти, но не можете запустить 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
}
}
}
Вот пример конфигурации для кэша на основе 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 обновления» должен стоять первым в списке, а «middleware извлечения» — последним. Подробности немного неясные, но см. Порядок 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 секунд в минуте.)
Время жизни кэша, установленное 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 для промежуточного программного обеспечения. Его можно использовать так:
@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. Вот старый 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, кэш промежуточного программного обеспечения на уровне сайта будет учитывать активный язык. Для тега 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, которые могут быть безопасно закодированы: строки, словари, списки объектов модели и так далее. (Большинство обычных объектов 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')
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."""
...
…и используйте путь к этому классу в Python в части BACKEND вашей настройки CACHES.
Кэши последующего уровня
До сих пор этот документ фокусировался на кэшировании ваших данных. Но для веб-разработки также важен другой тип кэширования: кэширование, выполняемое «потоком» или «последовательным». Это системы, которые кэшируют страницы для пользователей, даже до того, как запрос достигнет вашего веб-сайта.
Вот несколько примеров кэшей последующего уровня:
- Ваш интернет-провайдер может кэшировать определенные страницы, поэтому если вы запросили страницу с https://example.com/, ваш интернет-провайдер отправит вам страницу, не обращаясь напрямую к example.com. Разработчики example.com не знают об этом кэшировании; интернет-провайдер находится между example.com и вашим веб-браузером, обрабатывая все кэширование прозрачно.
- Ваш веб-сайт Django может размещаться за кэшем прокси, таким как Squid Web Proxy Cache (http://www.squid-cache.org/), который кэширует страницы для повышения производительности. В этом случае каждый запрос сначала обрабатывается прокси, и он передается вашему приложению только в случае необходимости.
- Ваш веб-браузер также кэширует страницы. Если веб-страница отправляет соответствующие заголовки, ваш браузер будет использовать локальную кэшированную копию для последующих запросов на эту страницу, не обращаясь к веб-странице повторно, чтобы проверить, изменилась ли она.
Кэширование последующего уровня — полезное улучшение производительности, но существует опасность: многие содержимые веб-страниц отличаются в зависимости от аутентификации и многих других переменных, а системы кэширования, которые слепо сохраняют страницы только на основе URL-адресов, могут раскрыть неверные или конфиденциальные данные последующим посетителям этих страниц.
END_OF_DOCUMENT_MARKERНапример, если вы управляете веб-системой электронной почты, содержимое страницы «входящие» зависит от того, какой пользователь вошел в систему. Если провайдер интернет-услуг (ISP) кеширует ваш сайт, то страница входящих сообщений первого пользователя, вошедшего через этого провайдера, будет кэширована для последующих посетителей сайта. Это не очень хорошо.
К счастью, 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_secondsno_cache=True
Полный список известных директив можно найти в реестре 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/3.1/topics/cache/