• 3.2
  • 5.0
  • 6.1
  • Версия документации: 3.1

Система кэширования Django

Фундаментальный компромисс динамических веб-сайтов заключается в том, что они динамичны. Каждый раз, когда пользователь запрашивает страницу, веб-сервер выполняет всевозможные вычисления — от запросов к базе данных до рендеринга шаблонов и бизнес-логики — чтобы создать страницу, которую увидит посетитель вашего сайта. С точки зрения затрат на обработку это намного дороже, чем стандартная схема сервера с чтением файла вне файловой системы.

Для большинства веб-приложений эти накладные расходы не имеют большого значения. Большинство веб-приложений не являются washingtonpost.com или slashdot.org; это сайты малого и среднего размера с посредственным трафиком. Но для сайтов со средней и высокой посещаемостью важно максимально сократить накладные расходы.

С этого момента в дело вступает кэширование.

To cache something is to save the result of an expensive calculation so that you don’t have to perform the calculation next time. Here’s some pseudocode explaining how this would work for a dynamically generated Web page:

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 also works well with «downstream» caches, such as Squid and browser-based caches. These are the types of caches that you don’t directly control but to which you can provide hints (via HTTP headers) about which parts of your site should be cached, and how.

См.также

Философия дизайна кэширующих фреймворков объясняет некоторые дизайн решения фреймворка.

Настройка кэша

Система кэширования требует небольшой настройки. А именно, надо указать где должны располагаться закэшированные данные – в базе данных, на файловой системе или прямо в памяти. Это важное решение, которое повлияет на производительность вашего кэша. Да, типы кэшей различаются по скорости работы.

Настройки кэша определяются параметром конфигурации CACHES. Ниже приведено объяснение всех возможных значений этого параметра.

Memcached

The fastest, most efficient type of cache supported natively by Django, Memcached__ is an entirely memory-based cache server, originally developed to handle high loads at LiveJournal.com and subsequently open-sourced by Danga Interactive. It is used by sites such as Facebook and Wikipedia to reduce database access and dramatically increase site performance.

Memcached работает как демон и захватывает определённый объём оперативной памяти. Его задачей является представление быстрого интерфейса для добавления, получения и удаления определённых данных в кэше. Все данные хранятся прямо в оперативной памяти, таким образом нет никакой дополнительной нагрузки на базу данных или файловую систему.

After installing Memcached itself, you’ll need to install a Memcached binding. There are several Python Memcached bindings available; the two most common are python-memcached and pylibmc.

Для использования Memcached с Django:

  • Set BACKEND to django.core.cache.backends.memcached.MemcachedCache or django.core.cache.backends.memcached.PyLibMCCache (depending on your chosen memcached binding)

  • Определите для LOCATION значение ip:port (где ip — это IP адрес, на котором работает демон Memcached, port — его порт) или unix:path (где path является путём к файлу-сокету Memcached).

In this example, Memcached is running on localhost (127.0.0.1) port 11211, using the python-memcached binding:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

In this example, Memcached is available through a local Unix socket file /tmp/memcached.sock using the python-memcached binding:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}

When using the pylibmc binding, do not include the unix:/ prefix:

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 следует сказать, что этот тип кэша имеет один недостаток: Кэш располагается в оперативной памяти и уничтожается при сбое сервера. Очевидно, что оперативная память не предназначена для постоянного хранения информации, поэтому не следует на неё рассчитывать в этом смысле. Несомненно, ни один из модулей кэширования не должен использоваться как постоянное хранилище — они предназначены для кэширования, не для хранения — мы особенно это отмечаем для данного типа кэша.

Кэширование в базу данных

Кэширование в базу данных отлично работает в случае, если у вас есть быстрый сервер баз данных с поддержкой индексирования.

Для использования таблицы базы данных в качестве бэкэнда кэша:

  • Установите 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',
    }
}

Создание таблицы для кэша

Before using the database cache, you must create the cache table with this command:

python manage.py createcachetable

В результате в базе данных будет создана таблица, структура которой соответствует ожиданиям системы кэширования. Имя для таблицы будет взято из параметра LOCATION.

При использовании нескольких БД кэшей, команда createcachetable создаст по одной таблице для каждого кэша.

Если вы используете множество баз данных, то команда createcachetable обратится к методу allow_migrate() роутера вашей базы данных (см. далее).

Аналогично команде migrate, команда createcachetable не внесёт изменения в существующую таблицу. Она создаёт только отсутствующие таблицы.

Чтобы вывести SQL, который был бы выполнен, без его выполнения, используйте опцию :djadminopt:`--dry-run`.

Использование нескольких баз данных

Если у вас несколько баз данных и вы планируете использовать кэширование, потребуется прописать инструкции роутинга для таблицы кэширования. В целях роутинга таблица кэширования представлена моделью CacheEntry в приложении django_cache. Эта модель не отобразится в модельном кэше, но содержимое модели может быть использовано для роутинга.

Например, представленный ниже роутер будет перенаправлять все операции чтения из кэша на cache_replica, а всё операции записи на cache_primary. Таблица кэширования будет синхронизироваться только с cache_primary:

class CacheRouter:
    """A router to control all database cache operations"""

    def db_for_read(self, model, **hints):
        "All cache read operations go to the replica"
        if model._meta.app_label == 'django_cache':
            return 'cache_replica'
        return None

    def db_for_write(self, model, **hints):
        "All cache write operations go to primary"
        if model._meta.app_label == 'django_cache':
            return 'cache_primary'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        "Only install the cache model on primary"
        if app_label == 'django_cache':
            return db == 'cache_primary'
        return None

Если вы не настроите роутинг для кэширования, то модуль кэширования будет использовать базу default.

А если вы не используете серверную часть кэша базы данных, вам не нужно беспокоиться о предоставлении инструкций маршрутизации для модели кэша базы данных.

Кэширование на файловую систему

Файловый бэкэнд сериализует и сохраняет каждое закэшированное значение в отдельном файле. Для использования этого бэкэнда, установите BACKEND в "django.core.cache.backends.filebased.FileBasedCache", а для LOCATION укажите подходящий каталог. Например, для хранения закэшированных данных в /var/tmp/django_cache, используйте такую настройку:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}

Если вы используете Windows, то подставьте букву диска в начало пути, вот так:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
    }
}

Путь до каталога должен быть абсолютным, т.е. он должен начинаться от корня файловой системы. Завершающий слэш не имеет значения.

Убедитесь, что каталог, на который указывает этот параметр, либо существует и доступен для чтения и записи, либо что он может быть создан пользователем системы, под которым работает ваш веб-сервер. Продолжая приведенный выше пример, если ваш сервер работает от имени пользователя «apache», убедитесь, что каталог «/var/tmp/django_cache» существует и доступен для чтения и записи пользователю «apache», или что он может быть создан пользователем «apache».

Кэширование в оперативной памяти

Это стандартный кэш, который применяется, если другой не определён в вашем файле конфигурации. Если вам требуется высокая скорость работы кэша, но у вас нет возможности развернуть Memcached, рассмотрите вариант использования кэша в оперативной памяти. Этот кэш по-процессный (см. далее) и потокобезопасный. Для его использования надо параметру конфигурации BACKEND присвоить значение "django.core.cache.backends.locmem.LocMemCache". Например:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

Параметр конфигурации LOCATION используется для идентификации отдельных хранилищ в памяти. Если у вас только один такой кэш, то вы можете пропустить этот параметр. Однако, в случае нескольких кэшей, вам потребуется назначить имя хотя бы для одного из них, для их различия.

Кэш использует стратегию отбраковки наименее недавно использованных данных (LRU).

Обратите внимание, что каждый процесс будет иметь свой собственный экземпляр частного кэша, что означает, что межпроцессное кэширование невозможно. Это также означает, что кэш локальной памяти не особенно эффективен с точки зрения использования памяти, поэтому он, вероятно, не является хорошим выбором для производственных сред. Это приятно для развития.

Псевдокэширование (для разработки)

Наконец, Django поставляется с «псевдо» кэшем, который не выполняет собственно кэширование. Он просто реализует интерфейс кэша, не делая больше ничего.

Он полезен в случае, когда у вас есть боевой сайт , который плотно использует кэширование в различных местах, а также окружение разработки или тестирование, где кэшировать ничего не надо. Для активации псевдо кэширования, установите BACKEND:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

Использование собственного модуля кэширования

Несмотря на то, что Django предоставляет ряд модулей кэширования, вам может потребоваться использовать свой кэширующий модуль. Для подключения внешнего кэширующего модуля используйте путь до него в качестве значения BACKEND параметра конфигурации CACHES, вот так:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

If you’re building your own backend, you can use the standard cache backends as reference implementations. You’ll find the code in the django/core/cache/backends/ directory of the Django source.

Следует отметить, если нет важной причины для использования собственного кэширующего модуля, вы должны использовать кэширующие модули, поставляемые с 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 backends pass the contents of OPTIONS as keyword arguments to the client constructors, allowing for more advanced control of client behavior. For example usage, see below.

  • 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
        }
    }
}

Here’s an example configuration for a python-memcached based backend with an object size limit of 2MB:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'server_max_value_length': 1024 * 1024 * 2,
        }
    }
}

Вот пример конфигурации для бэкенда на основе pylibmc, который включает двоичный протокол, аутентификацию SASL и режим поведения ketama:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'binary': True,
            'username': 'user',
            'password': 'pass',
            'behaviors': {
                'ketama': True,
            }
        }
    }
}

Кэш для каждого сайта

После настройки кэша, простейшим способом его использования будет кэширование всего сайта. Вам надо будет добавить 'django.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в параметр конфигурации MIDDLEWARE_CLASSES, как это показано в примере:

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

Примечание

Нет, это не ошибка: мидлварь «update» должна идти первой в списке, а мидлварь «fetch» — последней. Подробности тут не приводятся, обратитесь к Порядку классов MIDDLEWARE, проведенному далее, если они вам нужны.

Затем, добавьте следующие обязательные параметры в файл конфигурации проекта:

  • CACHE_MIDDLEWARE_ALIAS – Метка кэша, используемая для хранилища.

  • CACHE_MIDDLEWARE_SECONDS – The number of seconds each page should be cached.

  • CACHE_MIDDLEWARE_KEY_PREFIX – Если кэш разделён между несколькими сайтами внутри одной инсталляции Django, установите в имя сайта или любую другую строку, которая уникальна для этой инсталляции, чтобы предотвратить совпадения. Используйте пустую строку, если это не ваш случай.

Мидлварь FetchFromCacheMiddleware кэширует GET и HEAD отклики со статусом 200, если заголовки запроса и отклика это позволяют. Ответы на запросы для одного URL с разными параметрами запроса считаются уникальными и кэшируются раздельно. Мидлварь кэша ожидает, что запрос HEAD возвращает такие же заголовки, как и соответствующий GET запрос; в этом случае он может вернуть закэшированный GET отклик для запроса HEAD.

Кроме того, UpdateCacheMiddleware автоматически устанавливает несколько заголовков в каждом HttpResponse, которые влияют на нисходящие кэши:

  • Устанавливает в заголовке Expires текущие дату и время, добавляя к ним значение, определённое в CACHE_MIDDLEWARE_SECONDS.

  • Устанавливает заголовок Cache-Control, определяя максимальный возраст для страницы, также используя значение из параметра конфигурации CACHE_MIDDLEWARE_SECONDS.

Обратитесь к Промежуточный слой (Middleware) для подробностей.

Если представление устанавливает собственное время кэширования (т.е. определяет значение max-age в заголовке Cache-Control), то страница будет закэширована на указанное время, вместо CACHE_MIDDLEWARE_SECONDS. Используя декораторы из django.views.decorators.cache, вы можете легко определять время кэширования представлений (с помощью декоратора cache_control) или отключать кэширование для представления (с помощью декоратора never_cache). Обратитесь к разделу Использование других заголовков для получения дополнительной информации об этих декораторах.

Если параметр конфигурации USE_I18N установлен в True, то созданный ключ значения в кэше будет содержать имя активного языка – обратитесь к определению Django языковой настройки). Такое поведение позволяет легко кэшировать мультиязычные сайты, не требуя функционала для создания ключей кэша.

Cache keys also include the active language when USE_L10N is set to True and the current time zone when USE_TZ is set to True.

Кэширование на уровне представлений

django.views.decorators.cache.cache_page()

Более детальный способ использования системы кэширования возможен за счет кэширования вывода отдельных представлений. Модуль django.views.decorators.cache определяет декоратор cache_page, который автоматически кэширует вывод представления. Использовать его несложно:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

Декоратор cache_page принимает единственный аргумента: длительность кэширования, в секундах. В предыдущем примере, результат представления my_view() будет закэширован на 15 минут. (Следует отметить, что мы задали значения в виде 60 * 15 в целях читаемости. 60 * 15 будет вычислено в 900, т.е. 15 минут умножается на 60 секунд в минуте.)

Тайм-аут кэша, установленный в cache_page, имеет приоритет над директивой max-age из заголовка Cache-Control.

Кэш уровня представления, аналогично кэшу уровня сайта, использует ключи на основе URL. Если несколько URL указывают на одно представление, каждый URL будет закэширован отдельно. Продолжая работу с примером my_view, если ваш 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 для мидлвари. Он может быть использован следующим образом:

@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
    ...

Аргументы key_prefix и cache можно указать вместе. Аргумент key_prefix и параметр KEY_PREFIX настройки CACHES будут объединены.

Кроме того, cache_page автоматически устанавливает в ответе заголовки Cache-Control и Expires, которые влияют на нисходящие кэши.

Changed in Django 3.1:

In older versions, the max-age directive from the Cache-Control header had precedence over the cache timeout set by cache_page.

Определение кэша уровня представления в URLconf

Примеры из предыдущей секции содержат жёсткое определение кэширования представления, так как декоратор cache_page меняет поведение функции my_view. Такое подход связывает ваше представление с системой кэширования, что не всегда подходит по нескольким причинам. Например, вы можете пожелать распространять представления людям, которые захотят использовать их вне системы кэширования. Решением такой проблемы является указание кэширования в URLconf для каждого представления, а не у каждой функции.

Сделать это несложно: просто оберните функцию представления с помощью cache_page, при обращении к ней в URLconf. Вот так выглядела старая конфигурация URL:

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 %} кэширует содержимое блока на указанный период времени. Он принимает как минимум два аргумента: время кэширования в секундах и имя для закэшированного фрагмента. Имя будет использовано как есть, не используйте переменную. Например:

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

Временами требуется кэшировать многочисленные копии одного фрагмента, различающиеся только небольшим куском динамических данных. Например, может потребоваться кэширование копии сайдбара из предыдущего примера для каждого пользователя вашего сайта. Это можно сделать, передав дополнительные аргументы в шаблонный тег {% cache %}, что однозначно будет идентифицировать фрагмент:

{% load cache %}
{% cache 500 sidebar request.user.username %}
    .. sidebar for logged in user ..
{% endcache %}

Если параметр конфигурации USE_I18N установлен в True, то мидлварь кэширования сайта будет учитывать активных язык. Для шаблонного тега cache можно использовать одну из переменных, предназначенных для перевода, доступных шаблонам для достижения того же результата:

{% load i18n %}
{% load cache %}

{% get_current_language as LANGUAGE_CODE %}

{% cache 600 welcome LANGUAGE_CODE %}
    {% translate "Welcome to example.com" %}
{% endcache %}

Время жизни фрагмента в кэше также может быть шаблонной переменной, до тех пор пока эта переменная разрешается в целое значение. Например, если шаблонная переменная my_timeout имеет значение 600, то следующие два примера эквивалентны:

{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}

Эта особенность полезна для уменьшения количества повторений в шаблонах. Вы можете установить время жизни в переменной, в одном месте, и затем использовать это значение.

По умолчанию, кэширующий тег будет пытаться использовать кэш с именем «template_fragments». При отсутствии такого кэша, будет происходить переключение на стандартный кэш. Вы можете выбрать использование альтернативного бэкэнда кэша с помощью аргумента using, который должен быть последним аргументом в теге.

{% cache 300 local-thing ...  using="localcache" %}

Указание имени несконфигурированного кэша является ошибкой.

django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None)

Чтобы получить ключ кэша для кэшированного фрагменты шаблона, можно использовать функцию make_template_fragment_key. fragment_name – второй аргумент, передаваемый в тег cache, vary_on – список дополнительных аргументов, передаваемых в тег. Эту функцию можно использовать для переназначения или удаления кэшированных фрагментов шаблона:

>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
>>> key = make_template_fragment_key('sidebar', [username])
>>> cache.delete(key) # invalidates cached template fragment
True

API низкого уровня для кэширования

Временами кэширование всей созданной страницы не даёт нужного результата и обычно является неудобным излишеством.

Допустим, ваш сайт содержит представление, итог работы которого зависит от нескольких ресурсоёмких запросов, результаты которых периодически изменяются. В этом случае, кэширование всей страницы будет только вредить, но вот кэширование редко изменяемых фрагментов было бы хорошим подходом.

Для подобных случаев Django предоставляет простой API кэширования низкого уровня. Вы можете использовать этот API для хранения объектов в кэше с любым уровнем детализации. Вы можете поместить в кэш любой объект Python, который может быть безопасно сериализован («pickled»): строки, словари, списки объектов модели и так далее. (То есть, речь идёт о большинстве объектов языка 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

As a shortcut, the default cache is available as django.core.cache.cache:

>>> from django.core.cache import cache

Этот объект эквивалентен caches['default'].

Использование

Базовый интерфейс:

cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None)
>>> cache.set('my_key', 'hello, world!', 30)
cache.get(key, default=None, version=None)
>>> cache.get('my_key')
'hello, world!'

key должен быть str, а value может быть любым поддающимся выбору объектом Python.

Аргумент timeout является необязательным и обычно равен аргументу timeout соответствующего бэкенда кэша из параметре конфигурации CACHES (читайте выше). Аргумент определяет период в секундах, в течение которого значение должно храниться в кэше. Передав None в timeout можно закэшировать данные навсегда. При timeout равном 0 значение никогда не будет кэшироваться.

If the object doesn’t exist in the cache, cache.get() returns None:

>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None

If you need to determine whether the object exists in the cache and you have stored a literal value None, use a sentinel object as the default:

>>> 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

MemcachedCache

Due to a python-memcached limitation, it’s not possible to distinguish between stored None value and a cache miss signified by a return value of None on the deprecated MemcachedCache backend.

cache.get() can take a default argument. This specifies which value to return if the object doesn’t exist in the cache:

>>> cache.get('my_key', 'has expired')
'has expired'
cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)

To add a key only if it doesn’t already exist, use the add() method. It takes the same parameters as set(), but it will not attempt to update the cache if the key specified is already present:

>>> 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)

If you want to get a key’s value or set a value if the key isn’t in the cache, there is the get_or_set() method. It takes the same parameters as get() but the default is set as the new cache value for that key, rather than returned:

>>> cache.get('my_new_key')  # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

You can also pass any callable as a default 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)

There’s also a get_many() interface that only hits the cache once. get_many() returns a dictionary with all the keys you asked for that actually exist in the cache (and haven’t expired):

>>> 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)

To set multiple values more efficiently, use set_many() to pass a dictionary of key-value pairs:

>>> 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)

You can delete keys explicitly with delete() to clear the cache for a particular object:

>>> cache.delete('a')
True

delete() возвращает True, если ключ был успешно удален, False в противном случае.

Changed in Django 3.1:

The boolean return value was added.

cache.delete_many(keys, version=None)

If you want to clear a bunch of keys at once, delete_many() can take a list of keys to be cleared:

>>> cache.delete_many(['a', 'b', 'c'])
cache.clear()

Finally, if you want to delete all the keys in the cache, use cache.clear(). Be careful with this; clear() will remove everything from the cache, not just the keys set by your application.

>>> cache.clear()
cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)

cache.touch() sets a new expiration for a key. For example, to update a key to expire 10 seconds from now:

>>> 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)

You can also increment or decrement a key that already exists using the incr() or decr() methods, respectively. By default, the existing cache value will be incremented or decremented by 1. Other increment/decrement values can be specified by providing an argument to the increment/decrement call. A ValueError will be raised if you attempt to increment or decrement a nonexistent cache key.:

>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

Примечание

Методы incr() и decr() не гарантируют атомарность операции. Тут всё зависит от бэкенда. К примеру, Memcached гарантирует атомарность этих операций. Другие бэкенды делают это через двойную операцию считывания и сохранения нового значения.

cache.close()

Можно закрыть подключение к кэшу методом close(), если бэкенд кэширования предоставляет его.

>>> cache.close()

Примечание

Если кэш не реализует метод close, он будет пустым.

Прификсы ключей кэша

Если ваш кэш разделяется между несколькими серверами или между окружениями разработки и продакшена, возможна ситуация, когда данные, закэшированные одним сервером, будут использованы другим сервером. Если формат закэшированных данных различен между серверами, то это может привести к трудно выявляемым проблемам.

Чтобы избежать такой ситуации, Django предоставляет возможность назначения префиксов для всех ключей кэша, используемых сервером. При работе с ключом Django автоматически добавляет к нему префикс, определённый параметром конфигурации KEY_PREFIX.

Назначив различные префиксы для каждого экземпляра Django с помощью параметра конфигурации KEY_PREFIX, вы гарантируете отсутствие коллизий в значениях кэша.

Версионирование кэша

При изменении работающего кода, который использует закэшированные значения, может потребоваться сброс всех закэшированных данных. Простым способом является отчистка всего кэша, но это может привести к потере закэшированных данных, которые всё ещё верны и полезны.

Django предоставляет отличный способ выделения отдельных значений в кэше. Система кэширования Django обладает глобальным идентификатором версии, определённым в параметре кэша VERSION. Значение этого параметра автоматически объединяется с префиксом кэша и пользователем, которые предоставил ключ, получая итоговый ключ для обращения к кэшу.

By default, any key request will automatically include the site default cache key version. However, the primitive cache functions all include a version argument, so you can specify a particular cache key version to set or get. For example:

>>> # 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!'

The version of a specific key can be incremented and decremented using the incr_version() and decr_version() methods. This enables specific keys to be bumped to a new version, leaving other keys unaffected. Continuing our previous example:

>>> # 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 одного из ваших приложений:

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.

«Даунстрим» кэши

До сих пор этот документ был посвящен кэшированию ваших собственных данных. Но для веб-разработки актуален и другой тип кэширования: кэширование, выполняемое «нисходящими» кэшами. Это системы, которые кэшируют страницы пользователей еще до того, как запрос достигнет вашего сайта.

Рассмотрим несколько примеров таких «даунстрим» кэшей:

  • Your ISP may cache certain pages, so if you requested a page from https://example.com/, your ISP would send you the page without having to access example.com directly. The maintainers of example.com have no knowledge of this caching; the ISP sits between example.com and your Web browser, handling all of the caching transparently.

  • Ваше Django приложение может находится за прокси-кэшем, таким как Squid Web Proxy Cache (http://www.squid-cache.org/), который кэширует страницы для обеспечения производительности. В таком случае, каждый запрос сначала будет обработан прокси, а затем при необходимости будет передан вашему приложению.

  • Ваш веб-браузер тоже кэширует страницы. Если веб-страница отправляет соответствующие заголовки, ваш браузер будет использовать локальную кэшированную копию для последующих запросов к этой странице, даже не обращаясь к веб-странице повторно, чтобы узнать, изменилась ли она.

Последующее кэширование является хорошим повышением эффективности, но оно таит в себе опасность: содержимое многих веб-страниц различается в зависимости от аутентификации и множества других переменных, а системы кэширования, которые слепо сохраняют страницы исключительно на основе URL-адресов, могут предоставлять неверные или конфиденциальные данные последующим посетителям этих страниц.

For example, if you operate a Web email system, then the contents of the «inbox» page depend on which user is logged in. If an ISP blindly cached your site, then the first user who logged in through that ISP would have their user-specific inbox page cached for subsequent visitors to the site. That’s not cool.

К счастью, протокол HTTP предоставляет решение для этой проблемы. Существует ряд HTTP заголовков, которые инструктируют «даунстрим» кэши как именно следует различать страницы в зависимости от определённых переменных, также указывая что некоторые страницы кэшировать вообще не следует. Мы рассмотрим некоторые из этих заголовков в следующих секциях.

Использование заголовков Vary

Заголовок Vary определяет, какие заголовки запросов механизм кэширования должен учитывать при построении своего ключа кэша. Например, если содержимое веб-страницы зависит от языковых предпочтений пользователя, говорят, что страница «зависит от языка».

По-умолчанию, кэширующая система Django создаёт свои ключи, используя запрошенный путь и запрос, т.е., "https://www.example.com/stories/2005/?order_by=author". Это означает, что каждый запрос по этому URL-у будет использовать одну закэшированную версию, независимо от различия в куках или языковых настройках. Однако, если эта страница создаёт своё содержимое, основываясь на разнице в заголовках запроса, таких как куки или язык или тип браузера, вам следует использовать заголовок Vary, чтобы указать кэширующему механизму, что вывод данной страница зависит от этих параметров.

Чтобы сделать это в Django следует использовать декоратор представления django.views.decorators.vary.vary_on_headers(), так:

from django.views.decorators.vary import vary_on_headers

@vary_on_headers('User-Agent')
def my_view(request):
    ...

В этом случае кэширующий механизм (такой как кэширующий мидлварь Django) закэширует отдельную версию этой страницы для каждого уникального типа браузера.

The advantage to using the vary_on_headers decorator rather than manually setting the Vary header (using something like response['Vary'] = 'user-agent') is that the decorator adds to the Vary header (which may already exist), rather than setting it from scratch and potentially overriding anything that was already in there.

Вы можете передавать несколько заголовков в vary_on_headers():

@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
    ...

Это указывает «даунстрим» кэшам, что надо обращать внимание на оба, т.е. любая комбинация типа браузера и куки будет кэшироваться отдельно. Например, запрос от Mozilla с кукой foo=bar будет рассматриваться как отличный от запроса от Mozilla с кукой foo=ham.

Так как зависимость от куки является настолько стандартной, что существует декоратор django.views.decorators.vary.vary_on_cookie(). Следующие два представления эквивалентны:

@vary_on_cookie
def my_view(request):
    ...

@vary_on_headers('Cookie')
def my_view(request):
    ...

Заголовки, которые вы передаёте в vary_on_headers нечувствительны к регистру, т.е. "User-Agent" аналогичен "user-agent".

Также вы можете использовать вспомогательную функцию django.utils.cache.patch_vary_headers() напрямую. Эта функция устанавливает или добавляет заголовок Vary. Например:

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 в качестве первого аргумента и список/кортеж имён заголовков в качестве второго аргумента.

For more on Vary headers, see the official Vary spec.

Управление кэшированием: Использование других заголовков

Другими проблемами с кэшированием являются приватность данных и вопрос от том, где следует выполнять кэширование в каскаде кэшей.

Пользователь обычно сталкивается с двумя типами кешей: собственным кешем браузера (частный кеш) и кешем своего провайдера (публичный кеш). Публичный кеш используется несколькими пользователями и контролируется кем-то еще. Это создает проблемы с конфиденциальными данными — вы не хотите, чтобы, скажем, номер вашего банковского счета хранился в общедоступном кеше. Поэтому веб-приложениям нужен способ сообщать кэшам, какие данные являются частными, а какие общедоступными.

Решением является указание того, что страница должна кэшироваться в личном кэше. Для этого в Django следует использовать декоратор представления cache_control. Например:

from django.views.decorators.cache import cache_control

@cache_control(private=True)
def my_view(request):
    ...

Этот декоратор обеспечивает автоматическую отправку соответствующих HTTP заголовков.

Следует отметить, что настройки открытости и закрытости взаимоисключающие. Декоратор обеспечивает эту проверку. Примером использования таких настроек будет блог, который содержит закрытые и открытые записи. Открытые записи могут кэшироваться везде. Следующий код использует django.utils.cache.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

You can control downstream caches in other ways as well (see RFC 7234 for details on HTTP caching). For example, even if you don’t use Django’s server-side cache framework, you can still tell clients to cache a view for a certain amount of time with the max-age directive:

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, декоратор применит его, правильно объединив значения заголовка.)

Любая верная HTTP директива Cache-Control верна в cache_control(). Приведём полный список:

  • no_transform=True

  • must_revalidate=True

  • s_maxage=num_seconds

  • no_cache=True

Полный список известных директив можно найти в реестре IANA (обратите внимание, что не все из них применимы к ответам).

Если потребуется использовать заголовки, которые полностью отключают кэширование, то воспользуйтесь декоратором django.views.decorators.cache.never_cache(). Например:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

Порядок записей в MIDDLEWARE_CLASSES

Если вы используете кэширующую мидлварь, важно правильно её разместить в параметре конфигурации MIDDLEWARE_CLASSES. Так происходит из-за того, что кэширующая мидлварь должна знать какие заголовки как влияют на кэширующие системы. Мидлварь всегда добавляет что-то в заголовок Vary, когда может.

UpdateCacheMiddleware работает во время выдачи отклика, там где мидлвари применяются с конца списка, т.е. элемент наверху списка выполняется последним. Таким образом, надо удостовериться, что UpdateCacheMiddleware указан до любых мидлварей, которые могут добавить что-то в заголовок Vary. Следующие модули как раз такие:

  • SessionMiddleware добавляет Cookie

  • GZipMiddleware добавляет Accept-Encoding

  • LocaleMiddleware добавляет Accept-Language

FetchFromCacheMiddleware, с другой стороны, выполняется во время запроса, когда мидлвари применяются от начала списка, т.е. элемент наверху списка выполняется первым. FetchFromCacheMiddleware также должен выполняться после других мидлварей, которые изменяют заголовок Vary.

Back to Top