Система кэширования 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
Memcached — это полностью работающий в памяти сервер кэширования, изначально разработанный для обработки высоких нагрузок на LiveJournal.com, а впоследствии опубликованный компанией Danga Interactive как программное обеспечение с открытым исходным кодом. Такие сайты, как Facebook и Wikipedia, используют его для сокращения обращений к базе данных и значительного повышения производительности.
Memcached работает как служба и использует заданный объем оперативной памяти. Он предоставляет быстрый интерфейс для добавления, получения и удаления данных в кэше. Все данные хранятся непосредственно в памяти, поэтому накладных расходов на использование базы данных или файловой системы нет.
После установки самого Memcached нужно установить привязку для Memcached. Доступно несколько привязок Memcached для Python; Django поддерживает две из них: pylibmc и pymemcache.
Чтобы использовать Memcached с Django:
- Установите для
BACKENDзначениеdjango.core.cache.backends.memcached.PyMemcacheCacheилиdjango.core.cache.backends.memcached.PyLibMCCache(в зависимости от выбранной привязки Memcached). - Установите для
LOCATIONзначение в форматеip:port, гдеip— IP-адрес службы Memcached, аport— порт, на котором работает Memcached, либо значение в форматеunix:path, гдеpath— путь к файлу сокета Unix для Memcached.
В этом примере Memcached работает на локальном компьютере (127.0.0.1), порт 11211, с привязкой pymemcache:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "127.0.0.1:11211",
}
}
В этом примере Memcached доступен через локальный файл сокета Unix /tmp/memcached.sock с привязкой pymemcache:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "unix:/tmp/memcached.sock",
}
}
Одно из замечательных свойств Memcached — возможность совместно использовать кэш на нескольких серверах. Это означает, что можно запустить службы Memcached на нескольких машинах, а программа будет воспринимать их как единый кэш, не дублируя значения кэша на каждой машине. Чтобы воспользоваться этой возможностью, укажите в LOCATION адреса всех серверов в виде строки со значениями, разделенными точками с запятой или запятыми, либо в виде списка.
В этом примере кэш совместно используется экземплярами Memcached, запущенными по IP-адресам 172.19.26.240 и 172.19.26.242, оба — на порту 11211:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": [
"172.19.26.240:11211",
"172.19.26.242:11211",
],
}
}
В следующем примере кэш совместно используется экземплярами Memcached, запущенными по IP-адресам 172.19.26.240 (порт 11211), 172.19.26.242 (порт 11212) и 172.19.26.244 (порт 11213):
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": [
"172.19.26.240:11211",
"172.19.26.242:11212",
"172.19.26.244:11213",
],
}
}
По умолчанию серверная часть PyMemcacheCache задает следующие параметры (их можно переопределить в OPTIONS):
"OPTIONS": {
"allow_unicode_keys": True,
"default_noreply": False,
"serde": pymemcache.serde.pickle_serde,
}
И последнее замечание о Memcached: кэширование в памяти имеет один недостаток — поскольку кэшированные данные хранятся в памяти, при сбое сервера они будут потеряны. Очевидно, что память не предназначена для постоянного хранения данных, поэтому не полагайтесь на кэширование в памяти как на единственный способ хранения данных. Безусловно, ни одну из серверных частей кэширования Django не следует использовать для постоянного хранения: все они предназначены для кэширования, а не хранения. Мы обращаем на это внимание, поскольку кэширование в памяти особенно недолговечно.
Redis
Redis — это база данных в памяти, которую можно использовать для кэширования. Для начала потребуется запустить сервер Redis локально или на удаленной машине.
После настройки сервера Redis нужно установить привязки Python для Redis. redis-py — это привязка, которую Django поддерживает из коробки. Также рекомендуется установить пакет hiredis-py.
Чтобы использовать Redis в качестве серверной части кэша Django:
- Установите для
BACKENDзначениеdjango.core.cache.backends.redis.RedisCache. - Установите для
LOCATIONURL-адрес вашего экземпляра Redis, используя подходящую схему. См. документациюredis-py, где приведены сведения о доступных схемах.
Например, если Redis работает на локальном компьютере (127.0.0.1), порт 6379:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
}
}
Часто серверы Redis защищены аутентификацией. Чтобы указать имя пользователя и пароль, добавьте их в LOCATION вместе с URL-адресом:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://username:password@127.0.0.1:6379",
}
}
Если настроено несколько серверов Redis в режиме репликации, их можно указать в виде строки со значениями, разделенными точками с запятой или запятыми, либо в виде списка. При использовании нескольких серверов операции записи выполняются на первом сервере (лидере). Операции чтения выполняются на других серверах (репликах), выбранных случайным образом:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": [
"redis://127.0.0.1:6379", # leader
"redis://127.0.0.1:6378", # read-replica 1
"redis://127.0.0.1:6377", # read-replica 2
],
}
}
Кэширование в базе данных
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",
}
}
В отличие от других серверных частей кэширования, кэш базы данных не поддерживает автоматическую очистку просроченных записей на уровне базы данных. Вместо этого просроченные записи кэша удаляются при каждом вызове add(), set() или touch().
Создание таблицы кэша
Перед использованием кэша базы данных необходимо создать таблицу кэша с помощью этой команды:
python manage.py createcachetable
Она создает в базе данных таблицу в формате, который ожидает система кэширования Django для баз данных. Имя таблицы берется из LOCATION.
Если используется несколько кэшей базы данных, команда createcachetable создаст таблицу для каждого кэша.
Если используется несколько баз данных, команда createcachetable учитывает метод allow_migrate() маршрутизаторов баз данных (см. ниже).
Как и команда migrate, команда createcachetable не изменяет существующие таблицы. Она создает только отсутствующие таблицы.
Чтобы вывести SQL-команду, которая была бы выполнена, не запуская ее, используйте параметр createcachetable --dry-run.
Несколько баз данных
Если кэш базы данных используется с несколькими базами данных, потребуется также настроить правила маршрутизации для таблицы кэша. Для целей маршрутизации таблица кэша базы данных представлена как модель с именем CacheEntry в приложении с именем django_cache. Эта модель не отображается в кэше моделей, но сведения о ней можно использовать для маршрутизации.
Например, следующий маршрутизатор будет направлять все операции чтения кэша в cache_replica, а все операции записи — в cache_primary. Таблица кэша будет синхронизирована только с cache_primary:
class CacheRouter:
"""A router to control all database cache operations"""
def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
if model._meta.app_label == "django_cache":
return "cache_replica"
return None
def db_for_write(self, model, **hints):
"All cache write operations go to primary"
if model._meta.app_label == "django_cache":
return "cache_primary"
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
if app_label == "django_cache":
return db == "cache_primary"
return None
Если не задать правила маршрутизации для модели кэша базы данных, серверная часть кэша будет использовать базу данных default.
Если вы не используете серверную часть кэша базы данных, указывать правила маршрутизации для модели кэша базы данных не требуется.
Кэширование в файловой системе
Серверная часть на основе файлов сериализует каждое значение кэша и сохраняет его в отдельном файле. Чтобы использовать эту серверную часть, установите для BACKEND значение "django.core.cache.backends.filebased.FileBasedCache", а для LOCATION укажите подходящий каталог. Например, чтобы хранить кэшированные данные в /var/tmp/django_cache, используйте такую настройку:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "/var/tmp/django_cache",
}
}
В Windows укажите букву диска в начале пути, например:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "c:/foo/bar",
}
}
Путь к каталогу должен быть абсолютным, то есть начинаться от корня файловой системы. Неважно, заканчивается ли значение настройки косой чертой.
Убедитесь, что каталог, указанный в этой настройке, существует и доступен для чтения и записи либо что его может создать системный пользователь, от имени которого работает веб-сервер. Продолжая приведенный выше пример: если сервер работает от имени пользователя apache, убедитесь, что каталог /var/tmp/django_cache существует и доступен пользователю apache для чтения и записи либо что его может создать пользователь apache.
Предупреждение
Если расположение кэша LOCATION находится внутри MEDIA_ROOT, STATIC_ROOT или STATICFILES_FINDERS, конфиденциальные данные могут оказаться под угрозой.
Злоумышленник, получивший доступ к файлу кэша, может не только подделать HTML-контент, которому доверяет ваш сайт, но и удаленно выполнить произвольный код, поскольку данные сериализуются с помощью pickle.
Предупреждение
Кэширование в файловой системе может замедлиться при хранении большого количества файлов. Если вы столкнулись с этой проблемой, рассмотрите возможность использования другого механизма кэширования. Вы также можете создать подкласс FileBasedCache и улучшить стратегию очистки кэша.
Кэширование в локальной памяти
Это кэш по умолчанию, если в файле настроек не указана другая серверная часть. Если вам нужна высокая скорость кэширования в памяти, но вы не можете запустить Memcached, рассмотрите возможность использования серверной части кэша в локальной памяти. Этот кэш предназначен для отдельного процесса (см. ниже) и потокобезопасен. Чтобы использовать его, установите для BACKEND значение "django.core.cache.backends.locmem.LocMemCache". Например:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
}
}
Параметр кэша LOCATION используется для идентификации отдельных хранилищ в памяти. Если у вас только один кэш locmem, параметр LOCATION можно опустить; однако, если кэшей локальной памяти несколько, как минимум одному из них необходимо присвоить имя, чтобы разделить их.
Кэш использует стратегию очистки по принципу наименее недавно использовавшихся записей (LRU).
Обратите внимание, что у каждого процесса будет собственный закрытый экземпляр кэша, то есть кэширование между процессами невозможно. Это также означает, что кэш локальной памяти не особенно экономно расходует память и, вероятно, не подходит для промышленной среды. Для разработки он удобен.
Фиктивное кэширование (для разработки)
Наконец, Django включает «фиктивный» кэш, который на самом деле ничего не кэширует: он лишь реализует интерфейс кэша, не выполняя никаких действий.
Это полезно, если на рабочем сайте кэширование активно используется в разных местах, а в среде разработки или тестирования кэшировать ничего не нужно и вы не хотите менять код, чтобы предусмотреть отдельный случай для этой среды. Чтобы включить фиктивное кэширование, задайте для BACKEND следующее значение:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
}
}
Использование собственной серверной части кэша
Хотя Django из коробки поддерживает несколько серверных частей кэширования, иногда может потребоваться настроить собственную серверную часть. Чтобы использовать внешнюю серверную часть кэша с Django, укажите путь импорта Python в качестве значения BACKEND параметра CACHES, например:
CACHES = {
"default": {
"BACKEND": "path.to.backend",
}
}
Если вы разрабатываете собственную серверную часть, в качестве примеров можно использовать стандартные серверные части кэширования. Их код находится в каталоге django/core/cache/backends/ исходного кода Django.
Примечание. Если нет веской причины, например если хостинг их не поддерживает, используйте серверные части кэширования, входящие в состав Django. Они хорошо протестированы и подробно документированы.
Аргументы кэша
Каждой серверной части кэширования можно передать дополнительные аргументы для управления поведением кэша. Эти аргументы задаются дополнительными ключами параметра CACHES. Допустимы следующие аргументы:
-
TIMEOUT: время ожидания по умолчанию для кэша, в секундах. По умолчанию этот аргумент равен300секундам (5 минутам). Можно задать дляTIMEOUTзначениеNone, чтобы по умолчанию срок действия ключей кэша не истекал. Значение0означает, что срок действия ключей истекает немедленно (то есть кэширование не выполняется). -
OPTIONS: любые параметры, которые следует передать серверной части кэша. Допустимые параметры различаются для разных серверных частей. Серверные части, использующие стороннюю библиотеку, передают параметры непосредственно в эту библиотеку.Серверные части кэширования, реализующие собственную стратегию очистки (то есть серверные части
locmem,filesystemиdatabase), учитывают следующие параметры:-
MAX_ENTRIES: максимальное количество записей, допустимое в кэше до удаления устаревших значений. По умолчанию этот аргумент равен300. -
CULL_FREQUENCY: доля записей, удаляемых при достижении значенияMAX_ENTRIES. Фактическое отношение составляет1 / CULL_FREQUENCY, поэтому задайте дляCULL_FREQUENCYзначение2, чтобы при достиженииMAX_ENTRIESудалялась половина записей. Этот аргумент должен быть целым числом; его значение по умолчанию —3.Значение
0дляCULL_FREQUENCYозначает, что при достиженииMAX_ENTRIESбудет очищен весь кэш. В некоторых серверных частях (в частности,database) это значительно ускоряет очистку за счет увеличения числа промахов кэша.
Серверные части Memcached и Redis передают содержимое
OPTIONSв качестве именованных аргументов конструкторам клиентов, что позволяет гибко управлять поведением клиентов. Пример использования см. ниже. -
-
KEY_PREFIX: строка, которая автоматически добавляется (по умолчанию — в начало) ко всем ключам кэша, используемым сервером Django.Подробнее см. в документации по кэшированию.
-
VERSION: номер версии по умолчанию для ключей кэша, создаваемых сервером Django.Подробнее см. в документации по кэшированию.
-
KEY_FUNCTION: строка с точечным путем к функции, определяющей, как объединить префикс, версию и ключ в итоговый ключ кэша.Подробнее см. в документации по кэшированию.
В этом примере настраивается серверная часть на основе файлов с временем ожидания 60 секунд и максимальной емкостью 1000 элементов:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "/var/tmp/django_cache",
"TIMEOUT": 60,
"OPTIONS": {"MAX_ENTRIES": 1000},
}
}
Ниже приведен пример конфигурации серверной части на основе pylibmc, которая включает двоичный протокол, аутентификацию SASL и режим поведения ketama:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyLibMCCache",
"LOCATION": "127.0.0.1:11211",
"OPTIONS": {
"binary": True,
"username": "user",
"password": "pass",
"behaviors": {
"ketama": True,
},
},
}
}
Ниже приведен пример конфигурации серверной части на основе pymemcache, которая включает пул клиентов (что может повысить производительность за счет поддержания подключений клиентов), обрабатывает ошибки Memcached и сети как промахи кэша и устанавливает флаг TCP_NODELAY для сокета соединения:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "127.0.0.1:11211",
"OPTIONS": {
"no_delay": True,
"ignore_exc": True,
"max_pool_size": 4,
"use_pooling": True,
},
}
}
Ниже приведен пример конфигурации серверной части на основе redis, которая выбирает базу данных 10 (по умолчанию Redis поставляется с 16 логическими базами данных) и задает пользовательский класс пула соединений (по умолчанию используется redis.ConnectionPool):
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"db": "10",
"pool_class": "redis.BlockingConnectionPool",
},
}
}
Кэширование всего сайта
После настройки кэша самый простой способ использовать кэширование — кэшировать весь сайт. Добавьте 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в параметр MIDDLEWARE, как показано в этом примере:
MIDDLEWARE = [
"django.middleware.cache.UpdateCacheMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.cache.FetchFromCacheMiddleware",
]
Примечание
Нет, это не опечатка: промежуточное ПО «обновления» должно стоять первым в списке, а промежуточное ПО «извлечения» — последним. Причины не совсем очевидны, но если хотите разобраться, см. раздел Порядок MIDDLEWARE ниже.
Затем добавьте в файл настроек Django следующие обязательные параметры:
-
CACHE_MIDDLEWARE_ALIAS— псевдоним кэша, который будет использоваться для хранения данных. -
CACHE_MIDDLEWARE_SECONDS— целое число, задающее количество секунд, в течение которых кэшируется каждая страница. -
CACHE_MIDDLEWARE_KEY_PREFIX— если кэш совместно используется несколькими сайтами, работающими на одной установке Django, задайте имя сайта или другую строку, уникальную для этого экземпляра Django, чтобы предотвратить совпадение ключей. Если это неважно, используйте пустую строку.
FetchFromCacheMiddleware кэширует ответы на запросы GET и HEAD со статусом 200, если заголовки запроса и ответа это позволяют. Ответы на запросы к одному URL с разными параметрами запроса считаются отдельными страницами и кэшируются отдельно. Это промежуточное ПО предполагает, что на запрос HEAD возвращаются те же заголовки ответа, что и на соответствующий запрос GET; в таком случае оно может вернуть кэшированный ответ GET на запрос HEAD.
Кроме того, UpdateCacheMiddleware автоматически устанавливает несколько заголовков в каждом объекте HttpResponse, которые влияют на промежуточные кэши:
- Устанавливает заголовок
Expires, задавая текущую дату и время с добавлением значения параметраCACHE_MIDDLEWARE_SECONDS. - Устанавливает заголовок
Cache-Control, задавая максимальный срок хранения страницы, также на основе параметраCACHE_MIDDLEWARE_SECONDS.
Подробнее о промежуточном ПО см. в разделе Промежуточное ПО.
Если представление задает собственное время истечения срока действия кэша (то есть в его заголовке Cache-Control присутствует раздел max-age), страница будет кэшироваться до истечения этого срока, а не в течение периода, заданного параметром CACHE_MIDDLEWARE_SECONDS. С помощью декораторов из django.views.decorators.cache можно легко задать срок действия кэша для представления (с помощью декоратора cache_control()) или отключить кэширование для представления (с помощью декоратора never_cache()). Подробнее об этих декораторах см. в разделе Использование других заголовков.
Если для USE_I18N установлено значение True, в сгенерированный ключ кэша включается имя активного языка (см. также раздел Как Django определяет предпочитаемый язык). Это позволяет легко кэшировать многоязычные сайты без необходимости самостоятельно создавать ключ кэша.
Если для USE_TZ установлено значение True, в ключи кэша также включается текущий часовой пояс.
Кэширование отдельных представлений
-
django.views.decorators.cache.cache_page(timeout, *, cache=None, key_prefix=None)
Более гибкий способ использовать систему кэширования — кэшировать результат отдельных представлений. django.views.decorators.cache определяет декоратор cache_page, который автоматически кэширует ответ представления:
from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ...
cache_page принимает один аргумент: время ожидания кэша в секундах. В приведённом выше примере результат представления my_view() будет кэшироваться в течение 15 минут. (Для удобства чтения мы записали это как 60 * 15. 60 * 15 будет вычислено как 900 — то есть 15 минут, умноженные на 60 секунд в минуте.)
Время ожидания кэша, заданное с помощью cache_page, имеет приоритет над директивой max-age из заголовка Cache-Control.
Кэширование отдельных представлений, как и кэширование всего сайта, основано на URL. Если на одно и то же представление указывают несколько URL, для каждого URL создаётся отдельная запись в кэше. Продолжая пример с my_view, если ваш URLconf выглядит так:
urlpatterns = [
path("foo/<int:code>/", my_view),
]
то запросы к /foo/1/ и /foo/23/ будут кэшироваться отдельно, как и следовало ожидать. Однако после первого запроса к определённому URL (например, /foo/23/) последующие запросы к этому URL будут использовать кэш.
cache_page также может принимать необязательный именованный аргумент cache, который указывает декоратору использовать для кэширования результатов представления определённый кэш (из настройки CACHES). По умолчанию используется кэш default, но вы можете указать любой нужный кэш:
@cache_page(60 * 15, cache="special_cache") def my_view(request): ...
Вы также можете переопределить префикс кэша для отдельного представления. cache_page принимает необязательный именованный аргумент key_prefix, который работает так же, как настройка CACHE_MIDDLEWARE_KEY_PREFIX для middleware. Его можно использовать следующим образом:
@cache_page(60 * 15, key_prefix="site1") def my_view(request): ...
Аргументы key_prefix и cache можно указывать вместе. Аргумент key_prefix будет объединён с KEY_PREFIX, указанным в CACHES.
Кроме того, cache_page автоматически устанавливает в ответе заголовки Cache-Control и Expires, влияющие на промежуточные кэши.
Настройка кэширования представлений в URLconf
В примерах из предыдущего раздела тот факт, что представление кэшируется, задан напрямую, поскольку cache_page изменяет функцию my_view на месте. Такой подход связывает представление с системой кэширования, что по нескольким причинам нежелательно. Например, вы можете захотеть повторно использовать функции представлений на другом сайте без кэширования или предоставить представления тем, кто захочет использовать их без кэширования. Решение этих проблем — указывать кэширование представлений в URLconf, а не рядом с самими функциями представлений.
Для этого оберните функцию представления в cache_page, когда указываете её в URLconf. Вот URLconf из предыдущего примера:
urlpatterns = [
path("foo/<int:code>/", my_view),
]
А вот тот же вариант с my_view, обёрнутым в cache_page:
from django.views.decorators.cache import cache_page
urlpatterns = [
path("foo/<int:code>/", cache_page(60 * 15)(my_view)),
]
Кэширование фрагментов шаблона
Если вам нужен ещё более точный контроль, можно кэшировать фрагменты шаблона с помощью тега шаблона cache. Чтобы этот тег был доступен в шаблоне, поместите {% load cache %} в его начало.
Тег шаблона {% cache %} кэширует содержимое блока на заданное время. Он принимает как минимум два аргумента: время ожидания кэша в секундах и имя фрагмента кэша. Если время ожидания равно None, фрагмент кэшируется навсегда. Имя используется без изменений; не используйте переменную. Например:
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
Иногда может понадобиться кэшировать несколько копий фрагмента в зависимости от динамических данных, содержащихся в нём. Например, для каждого пользователя сайта может потребоваться отдельная кэшированная копия боковой панели из предыдущего примера. Для уникальной идентификации фрагмента кэша передайте тегу шаблона {% cache %} один или несколько дополнительных аргументов — переменных, с фильтрами или без них:
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged-in user ..
{% endcache %}
Если для USE_I18N задано значение True, кэш middleware для всего сайта будет учитывать активный язык. Чтобы получить тот же результат с тегом шаблона cache, можно использовать одну из переменных для переводов, доступных в шаблонах:
{% load i18n %}
{% load cache %}
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% translate "Welcome to example.com" %}
{% endcache %}
Время ожидания кэша может быть переменной шаблона, если её значение — целое число. Например, если переменной шаблона my_timeout присвоено значение 600, следующие два примера эквивалентны:
{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}
Эта возможность помогает избежать повторений в шаблонах. Можно задать время ожидания в переменной в одном месте и повторно использовать это значение.
По умолчанию тег кэширования пытается использовать кэш с именем «template_fragments». Если такого кэша нет, он использует кэш по умолчанию. Вы можете выбрать другой кэш-бэкенд с помощью именованного аргумента using, который должен быть последним аргументом тега.
{% cache 300 local-thing ... using="localcache" %}
Указание имени не настроенного кэша считается ошибкой.
-
django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None)
Чтобы получить ключ кэша для кэшированного фрагмента, используйте make_template_fragment_key. fragment_name — это второй аргумент тега шаблона cache; vary_on — список всех дополнительных аргументов, переданных тегу. Эта функция может пригодиться для аннулирования или перезаписи элемента кэша, например:
>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
>>> key = make_template_fragment_key("sidebar", [username])
>>> cache.delete(key) # invalidates cached template fragment
True
Низкоуровневый API кэширования
Иногда кэширование всей отрисованной страницы даёт мало пользы и, по сути, оказывается избыточным и неудобным.
Например, на вашем сайте может быть представление, результат которого зависит от нескольких ресурсоёмких запросов, причём результаты этих запросов меняются с разной периодичностью. В этом случае использовать кэширование целых страниц, предлагаемое стратегиями кэширования всего сайта или отдельных представлений, нецелесообразно: не нужно кэшировать весь результат, поскольку часть данных часто меняется, но при этом хочется кэшировать редко меняющиеся результаты.
Для таких случаев Django предоставляет низкоуровневый API кэширования. С его помощью можно сохранять в кэше объекты с любым нужным уровнем детализации. Можно кэшировать любые объекты Python, которые можно безопасно сериализовать с помощью pickle: строки, словари, списки объектов моделей и так далее. (Большинство распространённых объектов Python можно сериализовать с помощью pickle; подробности см. в документации Python.)
Доступ к кэшу
-
django.core.cache.caches -
Доступ к кэшам, настроенным в параметре
CACHES, можно получить через объект, похожий на словарь:django.core.cache.caches. Повторные запросы к одному и тому же псевдониму в одном потоке возвращают тот же объект.>>> from django.core.cache import caches >>> cache1 = caches["myalias"] >>> cache2 = caches["myalias"] >>> cache1 is cache2 True
Если ключ с указанным именем не существует, будет вызвано исключение
InvalidCacheBackendError.Для обеспечения потокобезопасности каждому потоку возвращается отдельный экземпляр кэш-бэкенда.
-
django.core.cache.cache -
Для удобства кэш по умолчанию доступен как
django.core.cache.cache:>>> from django.core.cache import cache
Этот объект эквивалентен
caches['default'].
Основные операции
Основной интерфейс:
-
cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None)
>>> cache.set("my_key", "hello, world!", 30)
-
cache.get(key, default=None, version=None)
>>> cache.get("my_key")
'hello, world!'
key должен быть объектом str, а value может быть любым объектом Python, который можно сериализовать с помощью pickle.
Аргумент timeout необязателен; по умолчанию используется значение аргумента timeout соответствующего бэкенда в настройке CACHES (описанной выше). Это количество секунд, в течение которых значение будет храниться в кэше. Если передать None для timeout, значение будет кэшироваться навсегда. Значение timeout, равное 0, отключает кэширование.
Если объект отсутствует в кэше, cache.get() возвращает None:
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get("my_key")
None
Если нужно определить, существует ли объект в кэше, и вы сохранили буквальное значение None, используйте в качестве значения по умолчанию объект-маркер:
>>> sentinel = object()
>>> cache.get("my_key", sentinel) is sentinel
False
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get("my_key", sentinel) is sentinel
True
cache.get() может принимать аргумент default. Он задаёт значение, которое следует вернуть, если объект отсутствует в кэше:
>>> cache.get("my_key", "has expired")
'has expired'
-
cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)
Чтобы добавить ключ, только если он ещё не существует, используйте метод add(). Он принимает те же параметры, что и set(), но не будет обновлять кэш, если указанный ключ уже существует:
>>> cache.set("add_key", "Initial value")
>>> cache.add("add_key", "New value")
>>> cache.get("add_key")
'Initial value'
Чтобы узнать, сохранил ли add() значение в кэше, проверьте возвращаемое значение. Метод вернёт True, если значение сохранено, и False в противном случае.
-
cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT, version=None)
Чтобы получить значение ключа или задать его, если ключ отсутствует в кэше, используйте метод get_or_set(). Он принимает те же параметры, что и get(), но значение по умолчанию становится новым значением кэша для этого ключа, а не возвращается:
>>> cache.get("my_new_key") # returns None
>>> cache.get_or_set("my_new_key", "my new value", 100)
'my new value'
В качестве значения по умолчанию также можно передать любой вызываемый объект:
>>> import datetime
>>> cache.get_or_set("some-timestamp-key", datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
-
cache.get_many(keys, version=None)
Также предусмотрен интерфейс get_many(), который обращается к кэшу только один раз. get_many() возвращает словарь со всеми запрошенными ключами, которые действительно существуют в кэше и срок действия которых не истёк:
>>> cache.set("a", 1)
>>> cache.set("b", 2)
>>> cache.set("c", 3)
>>> cache.get_many(["a", "b", "c"])
{'a': 1, 'b': 2, 'c': 3}
-
cache.set_many(dict, timeout)
Чтобы эффективнее задавать несколько значений, используйте set_many() и передайте ему словарь пар «ключ — значение»:
>>> cache.set_many({"a": 1, "b": 2, "c": 3})
>>> cache.get_many(["a", "b", "c"])
{'a': 1, 'b': 2, 'c': 3}
Как и cache.set(), set_many() принимает необязательный параметр timeout.
В поддерживаемых бэкендах (memcached) set_many() возвращает список ключей, которые не удалось добавить.
-
cache.delete(key, version=None)
Удалить ключи можно явно с помощью delete(), чтобы очистить кэш для определённого объекта:
>>> cache.delete("a")
True
delete() возвращает True, если ключ успешно удалён, и False в противном случае.
-
cache.delete_many(keys, version=None)
Чтобы за один раз очистить несколько ключей, передайте их список в delete_many():
>>> cache.delete_many(["a", "b", "c"])
-
cache.clear()
Наконец, чтобы удалить все ключи из кэша, используйте cache.clear(). Будьте осторожны: clear() удалит всё содержимое кэша, а не только ключи, установленные вашим приложением:
>>> cache.clear()
-
cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)
cache.touch() задаёт новый срок действия ключа. Например, чтобы задать срок истечения ключа через 10 секунд:
>>> cache.touch("a", 10)
True
Как и другие методы, аргумент timeout необязателен; по умолчанию используется параметр TIMEOUT соответствующего бэкенда в настройке CACHES.
touch() возвращает True, если срок действия ключа успешно обновлён, и False в противном случае.
-
cache.incr(key, delta=1, version=None)
-
cache.decr(key, delta=1, version=None)
Также можно увеличить или уменьшить значение существующего ключа с помощью методов incr() и decr() соответственно. По умолчанию существующее значение в кэше увеличивается или уменьшается на 1. Другие значения можно указать, передав аргумент методу увеличения или уменьшения. Если попытаться увеличить или уменьшить значение несуществующего ключа кэша, будет вызвано исключение ValueError:
>>> cache.set("num", 1)
>>> cache.incr("num")
2
>>> cache.incr("num", 10)
12
>>> cache.decr("num")
11
>>> cache.decr("num", 5)
6
Примечание
Методы incr()/decr() не гарантируют атомарность. В бэкендах, поддерживающих атомарное увеличение или уменьшение (в частности, в бэкенде memcached), эти операции атомарны. Однако если бэкенд не предоставляет встроенную операцию увеличения или уменьшения, она реализуется в два этапа: чтение и обновление.
-
cache.close()
Если кэш-бэкенд реализует этот метод, с помощью close() можно закрыть соединение с кэшем.
>>> cache.close()
Примечание
Для кэшей, в которых не реализованы методы close, вызов ничего не делает.
Примечание
Асинхронные варианты базовых методов имеют префикс a, например cache.aadd() или cache.adelete_many(). Подробнее см. раздел Асинхронная поддержка.
Префиксы ключей кэша
Если один экземпляр кэша используется несколькими серверами или в рабочей и среде разработки, данные, кэшированные одним сервером, могут быть использованы другим сервером. Если формат кэшированных данных на серверах различается, это может привести к проблемам, которые будет очень трудно диагностировать.
Чтобы этого избежать, Django позволяет добавлять префикс ко всем ключам кэша, используемым сервером. При сохранении или извлечении определённого ключа Django автоматически добавляет к нему значение настройки кэша KEY_PREFIX.
Если у каждого экземпляра Django задано своё значение KEY_PREFIX, коллизий значений в кэше не возникнет.
Версионирование кэша
При изменении работающего кода, использующего кэшированные значения, может понадобиться удалить существующие значения из кэша. Самый простой способ — очистить весь кэш, но это может привести к потере значений, которые всё ещё актуальны и полезны.
Django предлагает более удобный способ работы с отдельными значениями кэша. Система кэширования Django предусматривает глобальный идентификатор версии, задаваемый с помощью настройки кэша VERSION. Значение этой настройки автоматически объединяется с префиксом кэша и заданным пользователем ключом, образуя итоговый ключ кэша.
По умолчанию любой запрос ключа автоматически включает версию ключа кэша по умолчанию для сайта. Однако все низкоуровневые функции кэширования принимают аргумент version, поэтому можно указать конкретную версию ключа кэша при его сохранении или получении. Например:
>>> # Set version 2 of a cache key
>>> cache.set("my_key", "hello world!", version=2)
>>> # Get the default version (assuming version=1)
>>> cache.get("my_key")
None
>>> # Get version 2 of the same key
>>> cache.get("my_key", version=2)
'hello world!'
Версию определённого ключа можно увеличить или уменьшить с помощью методов incr_version() и decr_version(). Это позволяет перевести отдельные ключи на новую версию, не затрагивая остальные. Продолжим предыдущий пример:
>>> # Increment the version of 'my_key'
>>> cache.incr_version("my_key")
>>> # The default version still isn't available
>>> cache.get("my_key")
None
# Version 2 isn't available, either
>>> cache.get("my_key", version=2)
None
>>> # But version 3 *is* available
>>> cache.get("my_key", version=3)
'hello world!'
Преобразование ключей кэша
Как описано в двух предыдущих разделах, заданный пользователем ключ кэша используется не напрямую: он объединяется с префиксом и версией ключа, образуя итоговый ключ кэша. По умолчанию эти три части объединяются двоеточиями в итоговую строку:
def make_key(key, key_prefix, version):
return "%s:%s:%s" % (key_prefix, version, key)
Если вы хотите объединить эти части по-другому или обработать итоговый ключ (например, вычислить хэш-дайджест его частей), можно задать собственную функцию ключа.
Настройка кэша KEY_FUNCTION задаёт путь к функции в точечной нотации, соответствующей прототипу make_key() выше. Если эта пользовательская функция ключа задана, она будет использоваться вместо стандартной функции объединения ключей.
Предупреждения о ключах кэша
Memcached — наиболее распространённый кэш-бэкенд для рабочих сред — не допускает ключи кэша длиннее 250 символов, содержащие пробелы или управляющие символы; использование таких ключей вызывает исключение. Чтобы поощрять переносимость кода между кэш-бэкендами и избегать неприятных сюрпризов, другие встроенные кэш-бэкенды выдают предупреждение (django.core.cache.backends.base.CacheKeyWarning), если используется ключ, который вызвал бы ошибку в memcached.
Если вы используете в рабочей среде бэкенд, поддерживающий более широкий диапазон ключей (пользовательский бэкенд или один из встроенных бэкендов, отличных от memcached), и хотите использовать этот диапазон без предупреждений, подавите CacheKeyWarning с помощью следующего кода в модуле management одного из ваших приложений в INSTALLED_APPS:
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)
Если вместо этого вы хотите задать собственную логику проверки ключей для одного из встроенных бэкендов, создайте его подкласс, переопределите только метод validate_key и следуйте инструкциям по использованию собственного кэш-бэкенда. Например, чтобы сделать это для бэкенда locmem, поместите следующий код в модуль:
from django.core.cache.backends.locmem import LocMemCache
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""Custom validation, raising exceptions or warnings as needed."""
...
…и укажите путь к этому классу в точечной нотации в параметре BACKEND настройки CACHES.
Асинхронная поддержка
Django постепенно добавляет поддержку асинхронных кэш-бэкендов, но пока не поддерживает асинхронное кэширование. Эта возможность появится в одном из будущих выпусков.
django.core.cache.backends.base.BaseCache имеет асинхронные варианты всех базовых методов. По соглашению асинхронные версии всех методов имеют префикс a. По умолчанию аргументы обоих вариантов совпадают:
>>> await cache.aset("num", 1)
>>> await cache.ahas_key("num")
True
Промежуточные кэши
До сих пор в этом документе речь шла о кэшировании ваших собственных данных. Однако для веб-разработки важен и другой тип кэширования — кэширование «промежуточными» кэшами. Это системы, которые кэшируют страницы для пользователей ещё до того, как запрос достигает вашего сайта.
Вот несколько примеров промежуточных кэшей:
- При использовании HTTP ваш ISP может кэшировать определённые страницы. Поэтому, если вы запросили страницу с
http://example.com/, провайдер отправит её вам, не обращаясь напрямую к example.com. Владельцы example.com не знают о таком кэшировании: провайдер находится между example.com и вашим браузером и незаметно обрабатывает все операции кэширования. При использовании HTTPS такое кэширование невозможно, поскольку оно было бы атакой типа «человек посередине». - Ваш сайт на 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.headers['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.
Поскольку варьирование по 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
Управлять последующими кэшами можно и другими способами (подробности о кэшировании HTTP см. в документе RFC 9111). Например, даже если вы не используете серверную систему кэширования Django, вы всё равно можете указать клиентам кэшировать представление в течение определённого времени с помощью директивы max-age:
from django.views.decorators.cache import cache_control @cache_control(max_age=3600) def my_view(request): ...
(Если вы используете промежуточное ПО для кэширования, оно уже задаёт max-age со значением параметра CACHE_MIDDLEWARE_SECONDS. В этом случае пользовательский max_age из декоратора cache_control() будет иметь приоритет, а значения заголовков будут объединены правильно.)
Любая допустимая директива ответа Cache-Control допустима в cache_control(). Вот ещё несколько примеров:
no_transform=Truemust_revalidate=Truestale_while_revalidate=num_secondsno_cache=True
Полный список известных директив можно найти в реестре IANA (обратите внимание, что не все они применимы к ответам).
Если вы хотите полностью отключить кэширование с помощью заголовков, используйте декоратор представления never_cache(). Он добавляет заголовки, гарантирующие, что ответ не будет кэшироваться браузерами и другими кэшами. Пример:
from django.views.decorators.cache import never_cache @never_cache def myview(request): ...
Порядок работы MIDDLEWARE
Если вы используете промежуточное ПО для кэширования, важно разместить обе его части в правильных местах параметра MIDDLEWARE. Это необходимо, поскольку промежуточному ПО для кэширования нужно знать, по каким заголовкам следует варьировать хранилище кэша. Промежуточное ПО всегда добавляет что-либо в заголовок ответа Vary, если это возможно.
UpdateCacheMiddleware выполняется на этапе обработки ответа, когда промежуточное ПО запускается в обратном порядке. Поэтому элемент в начале списка выполняется последним на этапе обработки ответа. Следовательно, нужно убедиться, что UpdateCacheMiddleware находится перед любым другим промежуточным ПО, которое может что-либо добавить в заголовок Vary. Это делают следующие модули промежуточного ПО:
-
SessionMiddlewareдобавляетCookie -
GZipMiddlewareдобавляетAccept-Encoding -
LocaleMiddlewareдобавляетAccept-Language
С другой стороны, FetchFromCacheMiddleware выполняется на этапе обработки запроса, когда промежуточное ПО применяется по порядку, от начала списка к концу. Поэтому элемент в начале списка выполняется первым на этапе обработки запроса. FetchFromCacheMiddleware также должно выполняться после обновления заголовка Vary другим промежуточным ПО, поэтому FetchFromCacheMiddleware должно находиться после любого элемента, который это делает.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/cache/