Система кэширования Django
Ключевой компромисс динамических веб-сайтов заключается в их динамичности. Каждый раз, когда пользователь запрашивает страницу, веб-сервер выполняет различные вычисления — от запросов к базе данных до рендеринга шаблонов и бизнес-логики — для создания страницы, которую видит посетитель сайта. Это значительно дороже с точки зрения нагрузки на процессор, чем стандартный сервер, просто считывающий файл из файловой системы.
Для большинства веб-приложений эта нагрузка не критична. Большинство веб-приложений не washingtonpost.com или slashdot.org; это просто сайты среднего размера с умеренным трафиком. Но для сайтов со средним и высоким трафиком крайне важно минимизировать эту нагрузку.
Именно здесь появляется кэширование.
Кэширование — это сохранение результата дорогостоящего вычисления, чтобы не выполнять его повторно в следующий раз. Вот псевдокод, поясняющий, как это работает для динамически генерируемой веб-страницы:
given a URL, try finding that page in the cache
if the page is in the cache:
return the cached page
else:
generate the page
save the generated page in the cache (for next time)
return the generated page
Django поставляется с мощной системой кэширования, позволяющей сохранять динамические страницы, чтобы их не приходилось вычислять при каждом запросе. Для удобства Django предлагает различные уровни кэширования: можно кэшировать вывод конкретных представлений, кэшировать только труднодоступные фрагменты или кэшировать весь сайт.
Django также хорошо работает с «потоковыми» кэшами, такими как Squid и кэши браузера. Это типы кэшей, которыми вы не управляете напрямую, но можете давать подсказки (через HTTP-заголовки) о том, какие части вашего сайта следует кэшировать и как.
См. также
В философии дизайна системы кэширования объясняются некоторые решения по проектированию.
Настройка кэша
Система кэширования требует небольшой настройки. В частности, вам необходимо указать, где будут храниться ваши кэшированные данные — в базе данных, на файловой системе или непосредственно в памяти. Это важное решение, влияющее на производительность кэша; некоторые типы кэшей быстрее других.
Предпочтения относительно кэша указываются в настройке CACHES в вашем файле настроек. Вот объяснение всех доступных значений для CACHES.
Memcached
Самый быстрый и эффективный тип кэша, поддерживаемый Django, Memcached — это полностью основанный на памяти сервер кэша, первоначально разработанный для обработки больших нагрузок на LiveJournal.com и впоследствии открытый исходный код от Danga Interactive. Он используется такими сайтами, как Facebook и Wikipedia, для уменьшения доступа к базе данных и значительного повышения производительности сайта.
Memcached работает как демон и получает выделенный объем оперативной памяти. Он просто предоставляет быстрый интерфейс для добавления, извлечения и удаления данных из кэша. Все данные хранятся непосредственно в памяти, поэтому нет накладных расходов на использование базы данных или файловой системы.
После установки самого Memcached вам потребуется установить его обвязку. Существует несколько доступных Python-обвязок Memcached; две наиболее распространённые — 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 работает на локальном хосте (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-кэширующих бэкэндов не должен использоваться для постоянного хранения — они все предназначены для кэширования, а не для хранения — но мы указываем на это, поскольку кэширование на основе памяти является особенно временным.
Настройка LOCATION теперь поддерживает определение нескольких серверов в виде строки, разделённой запятыми.
Кэширование в базе данных
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) это делает очистку намного быстрее за счет увеличения промахов кэша.
Бэкэнды 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,
}
}
}
}
Теперь бэкэнды Memcached могут быть настроены с помощью OPTIONS.
В более ранних версиях вы могли передать параметры поведения pylibmc непосредственно внутри OPTIONS. Это устарело в пользу настройки этих параметров под ключом behaviors внутри OPTIONS.
Кэш на уровне сайта
После настройки кэша наиболее простым способом использования кэширования является кэширование всего сайта. Вам необходимо добавить 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в настройки MIDDLEWARE, как в этом примере:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
Примечание
Нет, это не опечатка: middleware «update» должен стоять первым в списке, а middleware «fetch» — последним. Подробности немного запутанные, но см. Порядок MIDDLEWARE ниже, если вы хотите узнать подробности.
Затем добавьте следующие необходимые настройки в свой файл настроек Django:
-
CACHE_MIDDLEWARE_ALIAS– псевдоним кэша для хранения. -
CACHE_MIDDLEWARE_SECONDS– количество секунд, в течение которых должна кэшироваться каждая страница. -
CACHE_MIDDLEWARE_KEY_PREFIX– Если кэш используется для нескольких сайтов, использующих одну и ту же установку Django, установите это значение в имя сайта или какую-либо другую строку, уникальную для данного экземпляра Django, чтобы предотвратить коллизии ключей. Используйте пустую строку, если вам все равно.
FetchFromCacheMiddleware кэширует ответы GET и HEAD со статусом 200, где запросы и заголовки ответов это разрешают. Ответы на запросы для одного и того же URL с разными параметрами запроса считаются уникальными страницами и кэшируются отдельно. Этот middleware ожидает, что запрос HEAD будет отвечать теми же заголовками ответа, что и соответствующий запрос GET; в таком случае он может возвращать кэшированный ответ GET для запроса HEAD.
Кроме того, UpdateCacheMiddleware автоматически устанавливает несколько заголовков в каждом HttpResponse:
- Устанавливает заголовок
Expiresна текущую дату/время плюс заданное значениеCACHE_MIDDLEWARE_SECONDS. - Устанавливает заголовок
Cache-Controlдля указания максимального срока действия страницы — снова, из настройкиCACHE_MIDDLEWARE_SECONDS.
В более ранних версиях также устанавливался заголовок Last-Modified.
См. 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, кэш middleware на уровне сайта будет учитывать активный язык. Для тега cache шаблона вы можете использовать одну из доступных в шаблонах переменных, специфичных для перевода, чтобы достичь такого же результата:
{% load i18n %}
{% load cache %}
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% trans "Welcome to example.com" %}
{% endcache %}
Время кэширования может быть переменной шаблона, при условии, что переменная шаблона разрешается в целочисленное значение. Например, если переменная шаблона my_timeout установлена в значение 600, то следующие два примера эквивалентны:
{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}
Эта функция полезна для избежания повторений в шаблонах. Вы можете установить время ожидания в переменную в одном месте и просто использовать это значение.
По умолчанию тег кэша будет пытаться использовать кэш с именем «template_fragments». Если такого кэша не существует, он перейдет к использованию стандартного кэша. Вы можете выбрать альтернативный кэш, используя ключевой аргумент using, который должен быть последним аргументом тега.
{% cache 300 local-thing ... using="localcache" %}
Указание имени кэша, который не настроен, считается ошибкой.
-
django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None)
Если вы хотите получить ключ кэша, используемый для кэшированного фрагмента, вы можете использовать make_template_fragment_key. fragment_name соответствует второму аргументу тега cache шаблона; vary_on — список всех дополнительных аргументов, переданных в тег. Эта функция может быть полезна для аннулирования или перезаписи кэшированного элемента, например:
>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
>>> key = make_template_fragment_key('sidebar', [username])
>>> cache.delete(key) # invalidates cached template fragment
API кэширования низкого уровня
Иногда кэширование всей страницы не приносит значительной выгоды и является избыточным.
Например, возможно, ваш сайт включает представление, результаты которого зависят от нескольких дорогостоящих запросов, результаты которых изменяются с разными интервалами. В этом случае было бы не идеально использовать кэширование всей страницы, которое предлагают стратегии кэширования на уровне сайта или представления, потому что вы не захотите кэшировать весь результат (поскольку некоторые данные часто меняются), но вы все равно захотите кэшировать результаты, которые редко изменяются.
Для таких случаев Django предоставляет простой API кэширования низкого уровня. Вы можете использовать этот API для хранения объектов в кэше с любой степенью детализации. Вы можете кэшировать любой объект Python, который можно безопасно сериализовать: строки, словари, списки объектов модели и т. д. (Большинство обычных объектов Python можно сериализовать; для получения дополнительной информации о сериализации см. документацию Python.)
Доступ к кэшу
-
django.core.cache.caches -
Вы можете получить доступ к кэшам, настроенным в параметре
CACHES, через объект, подобный словарю:django.core.cache.caches. Повторные запросы к одному и тому же псевдониму в одном потоке вернут один и тот же объект.>>> from django.core.cache import caches >>> cache1 = caches['myalias'] >>> cache2 = caches['myalias'] >>> cache1 is cache2 True
Если указанный ключ не существует,
InvalidCacheBackendErrorбудет поднято.Для обеспечения потокобезопасности для каждого потока будет возвращен другой экземпляр кэша.
-
django.core.cache.cache -
В качестве сокращения, стандартный кэш доступен как
django.core.cache.cache:>>> from django.core.cache import cache
Этот объект эквивалентен
caches['default'].
Базовое использование
Основной интерфейс — 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)
END_OF_DOCUMENT_MARKER Также есть интерфейс 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.
Обеспечивая различие префикса KEY_PREFIX для каждого экземпляра Django, вы можете гарантировать отсутствие коллизий в кэшированных значениях.
Версионирование кэша
Когда вы изменяете код, использующий кэшированные значения, вам может потребоваться очистить все существующие кэшированные значения. Самый простой способ сделать это – сбросить весь кэш, но это может привести к потере валидных и полезных кэшированных значений.
Django предоставляет лучший способ для таргетинга отдельных кэшированных значений. Система кэширования Django имеет глобальный идентификатор версии, указанный с помощью настройки кэша VERSION. Значение этой настройки автоматически комбинируется с префиксом кэша и предоставленным пользователем ключом кэша, чтобы получить окончательный ключ кэша.
По умолчанию любой запрос ключа автоматически включает версию ключа кэша по умолчанию. Однако все базовые функции кэша принимают аргумент version, поэтому вы можете указать определённую версию ключа кэша для установки или получения. Например:
>>> # Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
>>> # Get the default version (assuming version=1)
>>> cache.get('my_key')
None
>>> # Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'
Версию конкретного ключа можно инкрементировать и декрементировать, используя методы incr_version() и decr_version() соответственно. Это позволяет повысить версию определённых ключей, не затрагивая другие. Продолжая наш предыдущий пример:
>>> # Increment the version of 'my_key'
>>> cache.incr_version('my_key')
>>> # The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
>>> # But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'
Преобразование ключей кэша
Как описано в двух предыдущих разделах, предоставленный пользователем ключ кэша не используется дословно – он комбинируется с префиксом кэша и версией ключа, чтобы получить окончательный ключ кэша. По умолчанию три части соединяются с помощью двоеточий для создания конечной строки:
def make_key(key, key_prefix, version):
return ':'.join([key_prefix, str(version), key])
Если вы хотите комбинировать части другими способами или применять другую обработку к конечному ключу (например, вычисление хэш-суммы частей ключа), вы можете предоставить собственную функцию ключа.
Настройка кэша KEY_FUNCTION указывает путь с точками к функции, соответствующей прототипу make_key() выше. При предоставлении этой пользовательской функции ключа будет использоваться вместо стандартной функции объединения ключей.
Предупреждения о ключах кэша
Memcached, наиболее часто используемый бэкенд кэша в производстве, не позволяет использовать ключи кэша длиной более 250 символов или содержащие пробелы или управляющие символы, и использование таких ключей вызовет исключение. Чтобы поощрять код, совместимый с кэшированием, и минимизировать неприятные сюрпризы, другие встроенные бэкенды кэша выдают предупреждение (django.core.cache.backends.base.CacheKeyWarning) при использовании ключа, который может вызвать ошибку в memcached.
Если вы используете бэкенд производства, который может принимать более широкий диапазон ключей (пользовательский бэкенд или один из встроенных бэкендов, не являющихся memcached), и хотите использовать этот более широкий диапазон без предупреждений, вы можете отключить CacheKeyWarning с помощью этого кода в модуле management одного из ваших приложений INSTALLED_APPS:
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)
Если вы хотите предоставить собственную логику проверки ключей для одного из встроенных бэкендов, вы можете унаследовать его, переопределить только метод validate_key и следовать инструкциям по использованию пользовательского бэкенда кэша. Например, для бэкенда locmem поместите этот код в модуль:
from django.core.cache.backends.locmem import LocMemCache
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""Custom validation, raising exceptions or warnings as needed."""
...
…и используйте путь с точками к этому классу в разделе BACKEND вашей настройки CACHES.
Кэши последующего уровня
До сих пор этот документ фокусировался на кэшировании ваших данных. Но существует ещё один тип кэширования, относящийся к разработке веб-приложений: кэширование, выполняемое «последовательными» кэшами. Это системы, которые кэшируют страницы для пользователей даже до того, как запрос достигнет вашего веб-сайта.
Вот несколько примеров кэшей последующего уровня:
- Ваш интернет-провайдер может кэшировать определённые страницы, поэтому если вы запросили страницу с 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):
...
В этом случае механизм кэширования (например, собственный кэш-средство Django) будет кэшировать отдельную версию страницы для каждого уникального пользовательского агента.
Преимущество использования декоратора vary_on_headers вместо ручного задания заголовка Vary (например, response['Vary'] = 'user-agent') заключается в том, что декоратор добавляет к заголовку Vary (который может уже существовать), а не устанавливает его с нуля и потенциально перезаписывает всё, что уже там было.
Вы можете передавать несколько заголовков в vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
...
Это указывает кэшам последующего уровня на изменение в зависимости и от того, и от чего, а это означает, что каждая комбинация пользовательского агента и cookie получит своё собственное кэшированное значение. Например, запрос с пользовательским агентом Mozilla и значением cookie foo=bar будет считаться отличным от запроса с пользовательским агентом Mozilla и значением cookie foo=ham.
Поскольку изменение куки встречается часто, есть декоратор 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):
...
(Если вы используете кэширование посредством middleware, оно уже задаёт 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, важно разместить каждую часть в нужном месте в настройке MIDDLEWARE. Это необходимо, потому что middleware для кэширования должен знать, по каким заголовкам изменять хранение кэша. Middleware всегда добавляет что-то в заголовок ответа Vary когда это возможно.
UpdateCacheMiddleware выполняется на этапе ответа, где middleware выполняется в обратном порядке, поэтому элемент вверху списка выполняется последним на этапе ответа. Таким образом, вам нужно убедиться, что UpdateCacheMiddleware появляется перед любым другим middleware, который может добавить что-то в заголовок Vary. Это делают следующие модули middleware:
-
SessionMiddlewareдобавляетCookie -
GZipMiddlewareдобавляетAccept-Encoding -
LocaleMiddlewareдобавляетAccept-Language
FetchFromCacheMiddleware, с другой стороны, выполняется на этапе запроса, где middleware применяется от первого к последнему, поэтому элемент вверху списка выполняется первым на этапе запроса. FetchFromCacheMiddleware также должен запускаться после того, как другой middleware обновит заголовок Vary, поэтому FetchFromCacheMiddleware должен быть после любого элемента, который это делает.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/topics/cache/