Система кэширования 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 доступен через локальный файл сокета Unix /tmp/memcached.sock с использованием python-memcached связывания:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
При использовании pylibmc связывания не включайте префикс unix:/.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '/tmp/memcached.sock',
}
}
Отличной особенностью Memcached является возможность совместного использования кэша на нескольких серверах. Это означает, что вы можете запустить несколько демонов Memcached на разных машинах, и программа будет обрабатывать группу машин как единый кэш, без необходимости дублировать значения кэша на каждой машине. Чтобы воспользоваться этой функцией, включите все адреса серверов в LOCATION, разделённые точками с запятой или как список.
В этом примере кэш совместно используется экземплярами Memcached, работающими по IP-адресам 172.19.26.240 и 172.19.26.242, оба на порту 11211:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11211',
]
}
}
В следующем примере кэш совместно используется экземплярами Memcached, работающими по IP-адресам 172.19.26.240 (порт 11211), 172.19.26.242 (порт 11212) и 172.19.26.244 (порт 11213):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11212',
'172.19.26.244:11213',
]
}
}
Ещё один момент о Memcached: кэширование в оперативной памяти имеет недостаток: поскольку кэшированные данные хранятся в оперативной памяти, данные будут потеряны при сбое сервера. Очевидно, что оперативная память не предназначена для постоянного хранения данных, поэтому не полагайтесь на кэширование в оперативной памяти как на единственный способ хранения данных. Без сомнения, ни один из кэширующих бэкэндов Django не следует использовать для постоянного хранения — все они предназначены для кэширования, а не для хранения — но мы указываем на это здесь, потому что кэширование в оперативной памяти особенно временно.
Кэширование в базе данных
Django может хранить свои кэшированные данные в базе данных. Это лучше всего работает, если у вас есть быстрый сервер базы данных с хорошей индексацией.
Для использования таблицы базы данных в качестве бэкэнда кэша:
- Установите
BACKENDнаdjango.core.cache.backends.db.DatabaseCache - Установите
LOCATIONнаtablename, имя таблицы базы данных. Это имя может быть любым, при условии, что это допустимое имя таблицы, которое ещё не используется в вашей базе данных.
В этом примере имя таблицы кэша — my_cache_table:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
Создание таблицы кэша
Перед использованием кэша базы данных необходимо создать таблицу кэша с помощью этой команды:
python manage.py createcachetable
Это создаёт таблицу в базе данных в правильном формате, который ожидает система кэширования базы данных Django. Имя таблицы взято из LOCATION.
Если вы используете несколько кэшей базы данных, createcachetable создаёт одну таблицу для каждого кэша.
Если вы используете несколько баз данных, createcachetable учитывает метод маршрутизации allow_migrate() вашей базы данных (см. ниже).
Как и migrate, createcachetable не будет трогать существующую таблицу. Он будет создавать только отсутствующие таблицы.
Чтобы напечатать SQL-запрос, вместо его выполнения, используйте опцию createcachetable --dry-run.
Несколько баз данных
Если вы используете кэширование в базе данных с несколькими базами данных, вам также необходимо задать инструкции по маршрутизации для таблицы кэша базы данных. Для целей маршрутизации таблица кэша базы данных представлена как модель с именем CacheEntry, в приложении с именем django_cache. Эта модель не будет отображаться в кэше моделей, но данные модели могут использоваться для целей маршрутизации.
Например, следующий маршрутизатор перенаправит все операции чтения кэша в cache_replica, а все операции записи — в cache_primary. Таблица кэша будет синхронизирована только с cache_primary:
class CacheRouter(object):
"""A router to control all database cache operations"""
def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
if model._meta.app_label == 'django_cache':
return 'cache_replica'
return None
def db_for_write(self, model, **hints):
"All cache write operations go to primary"
if model._meta.app_label == 'django_cache':
return 'cache_primary'
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
if app_label == 'django_cache':
return db == 'cache_primary'
return None
Если вы не укажете направления маршрутизации для модели кэша базы данных, бэкенд кэша будет использовать базу данных default.
Конечно, если вы не используете бэкенд кэша базы данных, вам не нужно беспокоиться о предоставлении инструкций по маршрутизации для модели кэша базы данных.
Кэширование на файловой системе
Файловый бэкенд сериализует и хранит каждое значение кэша как отдельный файл. Для использования этого бэкэнда установите BACKEND на "django.core.cache.backends.filebased.FileBasedCache" и LOCATION на соответствующий каталог. Например, для хранения кэшированных данных в /var/tmp/django_cache, используйте эту настройку:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
}
}
Если вы работаете в Windows, поместите букву диска в начало пути, как в этом примере:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'c:/foo/bar',
}
}
Путь к каталогу должен быть абсолютным — то есть, он должен начинаться с корня файловой системы. Не имеет значения, ставите ли вы косую черту в конце настройки.
Убедитесь, что каталог, на который указывает эта настройка, существует и доступен для чтения и записи системному пользователю, под которым работает ваш веб-сервер. Продолжая предыдущий пример, если ваш сервер работает как пользователь apache, убедитесь, что каталог /var/tmp/django_cache существует и доступен для чтения и записи пользователем apache.
Кэширование в локальной памяти
Это кэш по умолчанию, если другой не указан в файле настроек. Если вам нужны преимущества кэширования в оперативной памяти, но у вас нет возможности запускать Memcached, рассмотрите кэш локальной памяти. Этот кэш относится к процессу (см. ниже) и потокобезопасен. Для использования установите BACKEND на "django.core.cache.backends.locmem.LocMemCache". Например:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
Кэш LOCATION используется для идентификации отдельных хранилищ памяти. Если у вас только один кэш locmem, вы можете опустить LOCATION; однако, если у вас более одного кэша локальной памяти, вам нужно будет присвоить имя хотя бы одному из них, чтобы они оставались отдельными.
Обратите внимание, что каждый процесс будет иметь свой собственный частный кэш, что означает, что кросс-процессное кэширование невозможно. Это также означает, что локальный кэш памяти не является особо эффективным с точки зрения памяти, поэтому он, вероятно, не является хорошим выбором для производственных сред. Он удобен для разработки.
Кэширование по умолчанию (для разработки)
Наконец, Django поставляется с кэшем «по умолчанию», который фактически не кэширует – он просто реализует интерфейс кэша, ничего не делая.
Это полезно, если у вас есть сайт производства, использующий мощное кэширование в разных местах, но среда разработки/тестирования, где вы не хотите кэшировать и не хотите изменять свой код для специального случая последней. Для активации кэширования по умолчанию установите BACKEND следующим образом:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
Использование пользовательского кэша
Хотя Django включает поддержку ряда кэшей по умолчанию, иногда вам может потребоваться использовать настроенный кэш. Чтобы использовать внешний кэш с Django, используйте путь импорта Python в качестве BACKEND параметра CACHES, как показано ниже:
CACHES = {
'default': {
'BACKEND': 'path.to.backend',
}
}
Если вы создаете свой собственный бэкенд, вы можете использовать стандартные кэши как эталонные реализации. Вы найдете код в каталоге django/core/cache/backends/ исходного кода Django.
Примечание: без действительно веской причины, такой как хост, который их не поддерживает, вы должны придерживаться кэшей, включенных в Django. Они хорошо протестированы и просты в использовании.
Аргументы кэша
Каждый кэш может быть предоставлен дополнительными аргументами для управления поведением кэширования. Эти аргументы предоставляются в виде дополнительных ключей в настройке CACHES. Допустимые аргументы следующие:
-
TIMEOUT: Время по умолчанию в секундах для использования в кэше. Этот аргумент по умолчанию равен300секундам (5 минут). Вы можете установитьTIMEOUTнаNone, чтобы по умолчанию ключи кэша никогда не истекали. Значение0заставляет ключи немедленно истекать (эффективно «не кэшировать»). -
OPTIONS: Любые параметры, которые должны быть переданы кэшу. Список допустимых параметров будет различаться для каждого бэкенда, и кэши, основанные на сторонней библиотеке, будут передавать свои параметры непосредственно в базовую библиотеку кэша.Кэши, которые реализуют собственную стратегию отсева (т. е.,
locmem,filesystemиdatabaseбэкенды), будут учитывать следующие параметры:-
MAX_ENTRIES: Максимальное количество элементов, разрешенных в кэше, прежде чем старые значения будут удалены. Этот аргумент по умолчанию равен300. -
CULL_FREQUENCY: Доля элементов, которые удаляются, когда достигаетсяMAX_ENTRIES. Фактическое соотношение равно1 / CULL_FREQUENCY, поэтому установитеCULL_FREQUENCYна2, чтобы удалить половину элементов, когда достигаетсяMAX_ENTRIES. Этот аргумент должен быть целым числом и по умолчанию равен3.Значение
0дляCULL_FREQUENCYозначает, что весь кэш будет сброшен, когда будет достигнутоMAX_ENTRIES. В некоторых бэкендах (в частности,database) это делает отсев намного быстрее за счет увеличения пропусков кэша.
-
-
KEY_PREFIX: Строка, которая будет автоматически включена (по умолчанию префикс) во все ключи кэша, используемые сервером Django.Дополнительную информацию см. в документации по кэшу.
-
VERSION: Номер версии по умолчанию для ключей кэша, сгенерированных сервером Django.Дополнительную информацию см. в документации по кэшу.
-
KEY_FUNCTIONСтрока, содержащая путь с точкой к функции, которая определяет, как составить префикс, версию и ключ в конечный ключ кэша.Дополнительную информацию см. в документации по кэшу.
В этом примере бэкенд файловой системы настраивается с временем ожидания 60 секунд и максимальной емкостью 1000 элементов:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
'TIMEOUT': 60,
'OPTIONS': {
'MAX_ENTRIES': 1000
}
}
}
Неверные аргументы игнорируются, как и неверные значения известных аргументов.
Кэш на сайт
После настройки кэша самым простым способом использования кэширования является кэширование всего сайта. Вам нужно добавить 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в настройку MIDDLEWARE, как в этом примере:
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:
- Устанавливает заголовок
Last-Modifiedв текущую дату/время, когда запрашивается свежая (не кэшированная) версия страницы. - Устанавливает заголовок
Expiresв текущую дату/время плюс заданное значениеCACHE_MIDDLEWARE_SECONDS. - Устанавливает заголовок
Cache-Controlдля указания максимального срока действия страницы – опять же, из настройкиCACHE_MIDDLEWARE_SECONDS.
Дополнительную информацию о middleware см. в разделе Middleware.
Если представление устанавливает собственное время истечения кэша (т. е. у него есть раздел max-age в заголовке Cache-Control) , то страница будет кэшироваться до времени истечения срока действия, а не CACHE_MIDDLEWARE_SECONDS. Используя декораторы в django.views.decorators.cache, вы можете легко установить время истечения срока действия представления (используя декоратор cache_control()) или отключить кэширование для представления (используя декоратор never_cache()). Дополнительную информацию об этих декораторах см. в разделе использование других заголовков.
Если USE_I18N установлено в True, то сгенерированный ключ кэша будет включать имя активного языка (см. также Как Django определяет предпочтение языка). Это позволяет легко кэшировать многоязычные сайты, не создавая ключ кэша самостоятельно.
Ключи кэша также включают активный язык, когда USE_L10N установлено в True, и текущую временную зону, когда USE_TZ установлено в True.
Кэш на представление
-
django.views.decorators.cache.cache_page()
Более тонкий способ использования системы кэширования заключается в кэшировании вывода отдельных представлений. django.views.decorators.cache определяет декоратор cache_page, который автоматически кэширует ответ представления. Он прост в использовании:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def my_view(request):
...
cache_page принимает один аргумент: время кэширования в секундах. В приведенном выше примере результат представления my_view() будет кэшироваться в течение 15 минут. (Обратите внимание, что мы написали его как 60 * 15 для удобства чтения. 60 * 15 будет вычислено как 900 – то есть 15 минут, умноженных на 60 секунд в минуте.)
Кэш на представление, как и кэш на сайт, использует URL в качестве ключа. Если несколько URL-адресов указывают на одно и то же представление, каждый URL-адрес будет кэшироваться отдельно. Продолжая пример my_view, если ваша конфигурация URL-адресов выглядит так:
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', my_view),
]
то запросы к /foo/1/ и /foo/23/ будут кэшироваться отдельно, как и ожидалось. Но как только определенный URL (например, /foo/23/) был запрошен, последующие запросы к этому URL-адресу будут использовать кэш.
cache_page также может принимать необязательный ключевой аргумент, cache, который направляет декоратор на использование определенного кэша (из вашего параметра CACHES) при кэшировании результатов представления. По умолчанию используется кэш default, но вы можете указать любой другой кэш:
@cache_page(60 * 15, cache="special_cache")
def my_view(request):
...
Вы также можете переопределить префикс кэша для каждого представления. cache_page принимает необязательный ключевой аргумент, key_prefix, который работает аналогично параметру CACHE_MIDDLEWARE_KEY_PREFIX для middleware. Его можно использовать так:
@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
...
Аргументы key_prefix и cache могут быть указаны вместе. Аргумент key_prefix и KEY_PREFIX, указанный в CACHES, будут конкатенированы.
Указание кэша для каждого представления в URLconf
Примеры в предыдущем разделе жестко кодируют тот факт, что представление кэшируется, потому что cache_page изменяет функцию my_view непосредственно. Такой подход связывает ваше представление с системой кэширования, что не является идеальным по нескольким причинам. Например, вы можете захотеть повторно использовать функции представления на другом сайте без кэширования или раздать их людям, которые могут их использовать без кэширования. Решение этих проблем заключается в указании кэша для каждого представления в URLconf, а не рядом с самими функциями представления.
Это легко сделать: просто оберните функцию представления с помощью cache_page при ее использовании в URLconf. Вот старый URLconf из предыдущего примера:
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', my_view),
]
Вот то же самое, но с my_view обернутым в cache_page:
from django.views.decorators.cache import cache_page
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]
Кэширование фрагментов шаблона
Если вам нужен еще больший контроль, вы также можете кэшировать фрагменты шаблонов, используя тег шаблона cache. Чтобы предоставить вашему шаблону доступ к этому тегу, поместите {% load cache %} в начале вашего шаблона.
Тег шаблона {% cache %} кэширует содержимое блока на заданный период времени. Он принимает как минимум два аргумента: время жизни кэша в секундах и имя кэшируемого фрагмента. Имя будет взято как есть, не используйте переменную. Например:
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
Иногда вам может потребоваться кэшировать несколько копий фрагмента в зависимости от динамических данных, которые появляются внутри фрагмента. Например, вам может понадобиться отдельный кэшированный экземпляр сайдбара, используемого в предыдущем примере, для каждого пользователя вашего сайта. Достигается это путем передачи дополнительных аргументов тегу шаблона {% cache %}, чтобы уникально идентифицировать кэшируемый фрагмент:
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
Совершенно допустимо указать более одного аргумента для идентификации фрагмента. Просто передайте столько аргументов тегу {% cache %} , сколько вам нужно.
Если USE_I18N установлен в True, кэш промежуточного слоя сайта будет учитывать активный язык. Для тега шаблона 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'].
Базовое использование
Базовый интерфейс — это set(key, value, timeout) и get(key):
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'
key должен быть str (или unicode в Python 2), а 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'
Чтобы добавить ключ только в том случае, если его еще нет, используйте метод add(). Он принимает те же параметры, что и set(), но не пытается обновить кэш, если указанный ключ уже существует:
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
Если вам нужно узнать, хранил ли add() значение в кэше, вы можете проверить возвращаемое значение. Оно вернет True, если значение было сохранено, и False в противном случае.
Если вам нужно получить значение ключа или установить значение, если ключ не находится в кэше, есть метод 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)
Добавлен метод get_or_set().
Также есть интерфейс get_many(), который обращается к кэшу только один раз. get_many() возвращает словарь со всеми ключами, которые вы запросили и которые фактически существуют в кэше (и не истекли):
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
Чтобы более эффективно установить несколько значений, используйте set_many() для передачи словаря пар «ключ-значение»:
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
Как и cache.set(), set_many() принимает необязательный параметр timeout.
Вы можете явно удалить ключи с помощью delete(). Это простой способ очистки кэша для определенного объекта:
>>> cache.delete('a')
Если вам нужно очистить сразу несколько ключей, delete_many() может принять список ключей для очистки:
>>> cache.delete_many(['a', 'b', 'c'])
И, наконец, если вы хотите удалить все ключи из кэша, используйте cache.clear(). Будьте осторожны; clear() удалит все из кэша, а не только ключи, установленные вашим приложением.
>>> cache.clear()
Вы также можете увеличить или уменьшить ключ, который уже существует, используя методы incr() или decr() соответственно. По умолчанию текущее значение кэша будет увеличено или уменьшено на 1. Другие значения для инкремента/декремента могут быть указаны путем предоставления аргумента вызову инкремента/декремента. ValueError будет выброшен, если вы попытаетесь увеличить или уменьшить несуществующий ключ кэша.:
>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6
Примечание
Методы incr()/decr() не гарантируют атомарности. В тех бекендах, которые поддерживают атомарные инкремент/декремент (в первую очередь, бекенд memcached), операции инкремента и декремента будут атомарными. Однако если бекенд не предоставляет операцию инкремента/декремента, она будет реализована с использованием двухоперационной схемы извлечения/обновления.
Вы можете закрыть соединение с кэшем с помощью close() , если оно реализовано кэш-бекендом.
>>> cache.close()
Примечание
Для кэшей, которые не реализуют методы close , это бесполезная операция.
Префикс ключа кэша
Если вы используете экземпляр кэша между серверами или между производственной и тестовой средами, возможно, данные, кэшированные одним сервером, будут использоваться другим сервером. Если формат кэшированных данных отличается между серверами, это может привести к проблемам, которые сложно диагностировать.
Для предотвращения этого Django предоставляет возможность префикса всех ключей кэша, используемых сервером. При сохранении или извлечении конкретного ключа кэша Django автоматически добавит к нему префикс, заданный значением параметра KEY_PREFIX настройки кэша.
Обеспечение того, что каждый экземпляр Django имеет другой KEY_PREFIX, гарантирует отсутствие конфликтов значений в кэше.
Версионирование кэша
При изменении работающего кода, использующего кэшированные значения, может потребоваться очистить все существующие кэшированные значения. Самый простой способ сделать это — очистить весь кэш, но это может привести к потере кэшированных значений, которые всё ещё актуальны и полезны.
Django предоставляет лучший способ нацеливания на отдельные кэшированные значения. В рамках фреймворка кэширования Django есть системный идентификатор версии, задаваемый настройкой кэша VERSION. Значение этой настройки автоматически объединяется с префиксом кэша и предоставленным пользователем ключом кэша для получения окончательного ключа кэша.
По умолчанию любой запрос ключа автоматически включает версию ключа кэша по умолчанию для сайта. Однако все базовые функции кэша принимают параметр version, поэтому вы можете указать конкретную версию ключа кэша для задания или получения. Например:
>>> # Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
>>> # Get the default version (assuming version=1)
>>> cache.get('my_key')
None
>>> # Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'
Версию конкретного ключа можно увеличить и уменьшить, используя методы incr_version() и decr_version(). Это позволяет изменять конкретные ключи на новую версию, не затрагивая другие. Продолжая наш предыдущий пример:
>>> # Increment the version of 'my_key'
>>> cache.incr_version('my_key')
>>> # The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
>>> # But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'
Преобразование ключа кэша
Как описано в предыдущих двух разделах, предоставленный пользователем ключ кэша не используется напрямую — он объединяется с префиксом кэша и версией ключа для формирования окончательного ключа кэша. По умолчанию три части объединяются двоеточием для получения окончательной строки:
def make_key(key, key_prefix, version):
return ':'.join([key_prefix, str(version), key])
Если вам нужно объединить части другими способами или применить другую обработку к окончательному ключу (например, вычислить хэш-сумму частей ключа), вы можете предоставить пользовательскую функцию ключа.
Настройка кэша KEY_FUNCTION указывает путь к функции с прототипом, соответствующим make_key() выше. При наличии эта пользовательская функция ключа будет использоваться вместо функции объединения ключей по умолчанию.
Предупреждения по ключам кэша
Memcached, наиболее часто используемый кэширующий бэкенд на производстве, не позволяет использовать ключи кэша длиной более 250 символов или содержащие пробелы или управляющие символы, и использование таких ключей вызовет исключение. Для поддержки портабельного кода кэша и минимизации неприятных сюрпризов другие встроенные бэкенды кэша выдают предупреждение (django.core.cache.backends.base.CacheKeyWarning) при использовании ключа, который вызовет ошибку в memcached.
Если вы используете бэкенд производства, который может принимать более широкий диапазон ключей (пользовательский бэкенд или один из встроенных бэкендов, не являющихся memcached), и хотите использовать этот более широкий диапазон без предупреждений, вы можете отключить CacheKeyWarning с помощью этого кода в модуле management одного из ваших INSTALLED_APPS:
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)
Если вы хотите предоставить собственную логику проверки ключей для одного из встроенных бэкендов, вы можете создать его подкласс, переопределить только метод validate_key и следовать инструкциям по использованию пользовательского бэкенда кэша. Например, для бэкенда locmem поместите этот код в модуль:
from django.core.cache.backends.locmem import LocMemCache
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""Custom validation, raising exceptions or warnings as needed."""
...
…и используйте путь к этой строке в Python в разделе 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-адресу будет использовать ту же кэшированную версию независимо от различий в пользовательском агенте, таких как cookie или языковые предпочтения. Однако, если эта страница создаёт разное содержимое на основе некоторых различий в заголовках запроса — таких как cookie, язык или пользовательский агент — вам необходимо использовать заголовок 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):
...
В этом случае механизм кэширования (например, собственный кэширующий middleware Django) будет кэшировать отдельную версию страницы для каждого уникального пользовательского агента.
Преимущества использования декоратора vary_on_headers вместо ручного задания заголовка Vary (используя что-то вроде response['Vary'] = 'user-agent') заключаются в том, что декоратор добавляет к заголовку Vary (который может уже существовать), а не устанавливает его заново и потенциально не переписывает уже существующее содержимое.
Вы можете передать несколько заголовков в vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
...
Это указывает кэшам последующего уровня менять содержимое в зависимости от обоих параметров, что означает, что для каждой комбинации пользовательского агента и cookie будет своё кэшированное значение. Например, запрос с пользовательским агентом Mozilla и значением cookie foo=ham будет считаться отличным от запроса с пользовательским агентом Mozilla и значением cookie foo=ham.
Поскольку изменение на основе cookie очень часто встречается, существует декоратор django.views.decorators.vary.vary_on_cookie(). Эти два представления эквивалентны:
@vary_on_cookie
def my_view(request):
...
@vary_on_headers('Cookie')
def my_view(request):
...
Передаваемые в vary_on_headers заголовки не чувствительны к регистру; "User-Agent" эквивалентно "user-agent".
Вы также можете использовать вспомогательную функцию django.utils.cache.patch_vary_headers() напрямую. Эта функция устанавливает или добавляет к Vary header. Например:
from django.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 в фоновом режиме.
Обратите внимание, что параметры управления кэшем «private» и «public» взаимоисключающие. Декоратор гарантирует, что директива «public» удаляется, если установлена «private» (и наоборот). Пример использования этих двух директив — сайт блога, который предлагает как частные, так и публичные записи. Публичные записи могут быть кэшированы в любом общем кэше. Следующий код использует 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/1.10/topics/cache/