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

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

For most Web applications, this overhead isn’t a big deal. Most Web applications aren’t washingtonpost.com or slashdot.org; they’re small- to medium-sized sites with so-so traffic. But for medium- to high-traffic sites, it’s essential to cut as much overhead as possible.

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

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

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. Ниже приведено объяснение всех возможных значений этого параметра.

Memcached

Самый быстрый и эффективный тип кэша, доступный Django, Memcached является кэшем, который полностью располагается в оперативной памяти, он был разработан для LiveJournal.com и позднее переведён в опенсорс компанией Danga Interactive. Он используется такими сайтами как Facebook и Wikipedia для снижения нагрузки на базу данных и значительного увеличения производительности сайта.

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

После установкам самого Memcached, следует установить его пакет для Python. Существует несколько таких пакетов; два наиболее используемых — python-memcached и pylibmc.

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

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

В этом примере Memcached запущен на localhost (127.0.0.1) порт 11211, используя python-memcached:

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

В этом примере Memcached доступен через локальный файл-сокет /tmp/memcached.sock, используя python-memcached:

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

При использовании pylibmc, не включайте префикс unix:/:

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

One excellent feature of Memcached is its ability to share a cache over multiple servers. This means you can run Memcached daemons on multiple machines, and the program will treat the group of machines as a single cache, without the need to duplicate cache values on each machine. To take advantage of this feature, include all server addresses in LOCATION, either as a semicolon or comma delimited string, or as a list.

В данном примере, кэш распределён по экземплярам 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',
    }
}

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

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

python manage.py createcachetable

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

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

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

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

To print the SQL that would be run, rather than run it, use the createcachetable --dry-run option.

Множество баз данных

Если у вас несколько баз данных и вы планируете использовать кэширование, потребуется прописать инструкции роутинга для таблицы кэширования. В целях роутинга таблица кэширования представлена моделью 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.

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

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

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

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

The cache uses a least-recently-used (LRU) culling strategy.

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

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

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

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

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

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

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

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

При создании своего кэширующего модуля вы можете использовать стандартные модули в качестве примера. Их код располагается в каталоге django/core/cache/backends/ исходного кода Django.

Note: Without a really compelling reason, such as a host that doesn’t support them, you should stick to the cache backends included with Django. They’ve been well-tested and are well-documented.

Параметры кэша

После определения типа кэша и имени для каждого из них, для каждого модуля можно указать дополнительные аргументы для управления поведением кэша. Эти аргументы представляются в виде дополнительных ключей в параметре конфигурации 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,
        }
    }
}

Here’s an example configuration for a pylibmc based backend that enables the binary protocol, SASL authentication, and the ketama behavior mode:

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

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

Once the cache is set up, the simplest way to use caching is to cache your entire site. You’ll need to add 'django.middleware.cache.UpdateCacheMiddleware' and 'django.middleware.cache.FetchFromCacheMiddleware' to your MIDDLEWARE setting, as in this example:

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

Примечание

No, that’s not a typo: the «update» middleware must be first in the list, and the «fetch» middleware must be last. The details are a bit obscure, but see Order of MIDDLEWARE below if you’d like the full story.

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

  • CACHE_MIDDLEWARE_ALIAS – Метка кэша, используемая для хранилища.
  • CACHE_MIDDLEWARE_SECONDS – Количество секунд хранения каждой закэшированной страницы.
  • 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) для подробностей.

If a view sets its own cache expiry time (i.e. it has a max-age section in its Cache-Control header) then the page will be cached until the expiry time, rather than CACHE_MIDDLEWARE_SECONDS. Using the decorators in django.views.decorators.cache you can easily set a view’s expiry time (using the cache_control() decorator) or disable caching for a view (using the never_cache() decorator). See the `using other headers`__ section for more on these decorators.

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

Ключи кэша также включают в себя активный язык в случаях, когда USE_L10N установлен в True, а также включают в себя текущий часовой пояс, если USE_TZ установлен в True.

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

django.views.decorators.cache.cache_page()

A more granular way to use the caching framework is by caching the output of individual views. django.views.decorators.cache defines a cache_page decorator that will automatically cache the view’s response for you:

from django.views.decorators.cache import cache_page

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

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

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

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

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

You can do so by wrapping the view function with cache_page when you refer to it in the URLconf. Here’s the old URLconf from earlier:

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 %} в начале шаблона.

The {% cache %} template tag caches the contents of the block for a given amount of time. It takes at least two arguments: the cache timeout, in seconds, and the name to give the cache fragment. The fragment is cached forever if timeout is None. The name will be taken as is, do not use a variable. For example:

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

Sometimes you might want to cache multiple copies of a fragment depending on some dynamic data that appears inside the fragment. For example, you might want a separate cached copy of the sidebar used in the previous example for every user of your site. Do this by passing one or more additional arguments, which may be variables with or without filters, to the {% cache %} template tag to uniquely identify the cache fragment:

{% 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 %}
    {% trans "Welcome to example.com" %}
{% endcache %}

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

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

This feature is useful in avoiding repetition in templates. You can set the timeout in a variable, in one place, and reuse that value.

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

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

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

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

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

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

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

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

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

For cases like this, Django exposes a low-level cache API. You can use this API to store objects in the cache with any level of granularity you like. You can cache any Python object that can be pickled safely: strings, dictionaries, lists of model objects, and so forth. (Most common Python objects can be pickled; refer to the Python documentation for more information about pickling.)

Доступ к кэшу

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'].

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

The basic interface is:

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 should be a str, and value can be any picklable Python object.

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

Если объект отсутствует в кэше, то cache.get() возвращает None:

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

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

Метод cache.get() принимает аргумент default. Он определяет значение, которое будет возвращено, если указанного объекта нет в кэше:

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

Для того, чтобы добавить элемент в кэш только в случае, когда его там нет, следует использовать метод add(). Он принимает такие же параметры, как и метод set(), но не будет пытаться изменить значение, если оно уже присутствует в кэше:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

Если вам надо определить сохранил ли метод add() значение в кэше, вы можете проверить значение, которое он возвращает. True указывает, что значение было сохранено, а False – наоборот.

cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT, version=None)

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'

Вы также можете передать функцию как значение по умолчанию:

>>> 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() можно получить ряд значений из кэша за один запрос. Этот метод возвращает словарь со всеми запрошенными ключами, которые действительно присутствуют в кэше (и имеют актуальный срок действия):

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

On supported backends (memcached), set_many() returns a list of keys that failed to be inserted.

cache.delete(key, version=None)

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

>>> cache.delete('a')
cache.delete_many(keys, version=None)

Для одновременного удаления множества значений надо использовать метод delete_many(), который принимает список ключей, подлежащих удалению:

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

Наконец, для полной очистки кэша надо использовать метод cache.clear(). Будьте осторожными с этим методом. Метод удаляет из кэша всё, не только ключи, установленные вашим приложением.

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

Like other methods, the timeout argument is optional and defaults to the TIMEOUT option of the appropriate backend in the CACHES setting.

touch() returns True if the key was successfully touched, False otherwise.

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. Значение этого параметра автоматически объединяется с префиксом кэша и пользователем, которые предоставил ключ, получая итоговый ключ для обращения к кэшу.

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

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

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

from django.core.cache.backends.locmem import LocMemCache

class CustomLocMemCache(LocMemCache):
    def validate_key(self, key):
        """Custom validation, raising exceptions or warnings as needed."""
        ...

…и используйте путь в точечной нотации к этому классу в ключе BACKEND параметра конфигурации CACHES.

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

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

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

  • Ваш провайдер может кэшировать определённые страницы, т.е. если вы запросите страницу с https://example.com/, то провайдер пришлёт её вам без обращения к самому сайту. Основатели данного сайта даже не будут знать о таком кэшировании, так как провайдер находится между вашим браузером и сайтом, незаметно выполняя кэширование.
  • Ваше 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 заголовков, которые инструктируют «даунстрим» кэши как именно следует различать страницы в зависимости от определённых переменных, также указывая что некоторые страницы кэшировать вообще не следует. Мы рассмотрим некоторые из этих заголовков в следующих секциях.

Using Vary headers

Заголовок 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) закэширует отдельную версию этой страницы для каждого уникального типа браузера.

Преимущество использования декоратора vary_on_headers по сравнению с ручной установкой заголовка Vary (с помощью response['Vary'] = 'user-agent') в том, что декоратор добавляет значение к заголовку Vary (который может уже существовать), а не переопределяет его.

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

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

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

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

@vary_on_cookie
def my_view(request):
    ...

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

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

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

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.

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

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

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

The solution is to indicate a page’s cache should be «private.» To do this in Django, use the cache_control() view decorator. Example:

from django.views.decorators.cache import cache_control

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

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

Note that the cache control settings «private» and «public» are mutually exclusive. The decorator ensures that the «public» directive is removed if «private» should be set (and vice versa). An example use of the two directives would be a blog site that offers both private and public entries. Public entries may be cached on any shared cache. The following code uses patch_cache_control(), the manual way to modify the cache control header (it is internally called by the cache_control() decorator):

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

(If you do use the caching middleware, it already sets the max-age with the value of the CACHE_MIDDLEWARE_SECONDS setting. In that case, the custom max_age from the cache_control() decorator will take precedence, and the header values will be merged correctly.)

Any valid Cache-Control response directive is valid in cache_control(). Here are some more examples:

  • no_transform=True
  • must_revalidate=True
  • stale_while_revalidate=num_seconds

The full list of known directives can be found in the IANA registry (note that not all of them apply to responses).

If you want to use headers to disable caching altogether, never_cache() is a view decorator that adds headers to ensure the response won’t be cached by browsers or other caches. Example:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

Order of MIDDLEWARE

If you use caching middleware, it’s important to put each half in the right place within the MIDDLEWARE setting. That’s because the cache middleware needs to know which headers by which to vary the cache storage. Middleware always adds something to the Vary response header when it can.

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

  • SessionMiddleware добавляет Cookie
  • GZipMiddleware добавляет Accept-Encoding
  • LocaleMiddleware добавляет Accept-Language

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