Spec-Zone.ru › Django 2.0

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

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

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

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

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

given a URL, try finding that page in the cache
if the page is in the cache:
    return the cached page
else:
    generate the page
    save the generated page in the cache (for next time)
    return the generated page

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

Django также хорошо работает с «потоками» кэшей, такими как Squid и кэши браузера. Это типы кэшей, которыми вы не управляете напрямую, но которым можете давать подсказки (через HTTP-заголовки) о том, какие части вашего сайта следует кэшировать и как.

См. также

В философии проектирования системы кэширования объясняется несколько решений, принятых при разработке этой системы.

Настройка кэширования

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

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

Memcached

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

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

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

Чтобы использовать Memcached с Django:

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

В этом примере Memcached работает на локальном хосте (127.0.0.1) на порту 11211, используя привязку python-memcached.

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

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

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

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

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

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

В этом примере кэш используется совместно экземплярами Memcached, работающими по IP-адресам 172.19.26.240 и 172.19.26.242, оба на порту 11211:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

В следующем примере кэш используется совместно экземплярами Memcached, работающими по IP-адресам 172.19.26.240 (порт 11211), 172.19.26.242 (порт 11212) и 172.19.26.244 (порт 11213):

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11212',
            '172.19.26.244:11213',
        ]
    }
}

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

Изменено в Django 1.11:

Настройка LOCATION теперь поддерживает определение нескольких серверов как строки, разделенной запятыми.

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

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

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

  • Установите BACKEND на django.core.cache.backends.db.DatabaseCache
  • Установите LOCATION на tablename, имя таблицы базы данных. Это имя может быть любым, пока это допустимое имя таблицы, которое ещё не используется в вашей базе данных.

В этом примере имя таблицы кэша — my_cache_table:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}

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

Перед использованием кэша базы данных необходимо создать таблицу кэша с помощью этой команды:

python manage.py createcachetable

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

Если вы используете несколько кэшей базы данных, createcachetable создаёт по одной таблице для каждого кэша.

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

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

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

Несколько баз данных

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Хотя Django включает поддержку ряда бэкендов кэша «из коробки», иногда вам может потребоваться использовать настраиваемый бэкенд кэша. Для использования внешнего бэкенда кэша с Django используйте путь импорта Python в качестве BACKEND настройки CACHES, например:

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

Если вы создаёте свой собственный бэкенд, вы можете использовать стандартные бэкенды кэша в качестве справочных реализаций. Код вы найдёте в каталоге django/core/cache/backends/ исходного кода Django.

Примечание: Без веской причины, такой как хостинг, который их не поддерживает, вы должны использовать бэкенды кэша, включенные в Django. Они хорошо протестированы и просты в использовании.

Аргументы кэша

Каждый бэкенд кэша может принимать дополнительные аргументы для управления поведением кэширования. Эти аргументы предоставляются в качестве дополнительных ключей в настройке CACHES. Допустимые аргументы следующие:

  • TIMEOUT: Значение по умолчанию для времени ожидания кэширования в секундах. По умолчанию этот аргумент равен 300 секундам (5 минут). Вы можете установить TIMEOUT на None, чтобы ключи кэша по умолчанию никогда не истекали. Значение 0 заставляет ключи немедленно истекать (эффективно «не кэшировать»).
  • OPTIONS: Любые параметры, которые должны передаваться в бэкенд кэша. Список допустимых параметров будет различаться в зависимости от бэкенда, а бэкенды кэша, основанные на сторонней библиотеке, будут передавать свои параметры напрямую в базовую библиотеку кэша.

    Бэкенды кэша, реализующие собственную стратегию отбора (т. е. бэкенды locmem, filesystem и database ) будут учитывать следующие параметры:

    • MAX_ENTRIES: Максимальное количество записей, разрешённых в кэше, прежде чем старые значения будут удалены. По умолчанию этот аргумент равен 300.
    • CULL_FREQUENCY: Доля записей, которые будут удалены, когда MAX_ENTRIES будет достигнута. Фактическое соотношение составляет 1 / CULL_FREQUENCY, поэтому установите CULL_FREQUENCY в 2, чтобы удалить половину записей, когда MAX_ENTRIES будет достигнута. Этот аргумент должен быть целым числом и по умолчанию равен 3.

      Значение 0 для CULL_FREQUENCY означает, что весь кэш будет очищен, когда MAX_ENTRIES будет достигнуто. В некоторых бэкендах (в частности, database) это делает отбор намного быстрее за счёт увеличения пропусков кэша.

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

  • KEY_PREFIX: Строка, которая будет автоматически включаться (в начале по умолчанию) во все ключи кэша, используемые сервером Django.

    Дополнительную информацию см. в разделе документация по кэшу.

  • VERSION: Номер версии по умолчанию для ключей кэша, генерируемых сервером Django.

    Дополнительную информацию см. в разделе документация по кэшу.

  • KEY_FUNCTION: Строка, содержащая путь к функции, которая определяет, как объединить префикс, версию и ключ в конечный ключ кэша.

    Дополнительную информацию см. в разделе документация по кэшу.

В этом примере конфигурируется файловый бэкенд кэша с временем ожидания 60 секунд и максимальной ёмкостью 1000 элементов:

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

Вот пример конфигурации для бэкенда, основанного на python-memcached, с ограничением размера объекта 2 МБ:

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

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

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'binary': True,
            'username': 'user',
            'password': 'pass',
            'behaviors': {
                'ketama': True,
            }
        }
    }
}
Изменено в Django 1.11:

Бэкенды Memcached теперь могут настраиваться с использованием OPTIONS.

В более старых версиях вы могли передавать параметры поведения pylibmc непосредственно внутри OPTIONS. Это устарело и рекомендуется настраивать их под ключом behaviors в OPTIONS.

Кэш на сайт

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

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

Примечание

Это не опечатка: «middleware обновления» должен стоять первым в списке, а «middleware извлечения» — последним. Подробности немного запутанны, но вы можете ознакомиться с Порядок MIDDLEWARE ниже, если хотите узнать все подробности.

Затем добавьте необходимые настройки в файл настроек Django:

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

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

Кроме того, UpdateCacheMiddleware автоматически устанавливает несколько заголовков в каждом HttpResponse:

  • Устанавливает заголовок Expires на текущую дату/время плюс заданное CACHE_MIDDLEWARE_SECONDS.
  • Устанавливает заголовок Cache-Control, чтобы указать максимальную продолжительность отображения страницы — опять же, из настройки CACHE_MIDDLEWARE_SECONDS.
Изменено в Django 1.11:

В более ранних версиях также устанавливался заголовок Last-Modified.

См. Middleware для получения дополнительной информации о middleware.

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

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

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

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

django.views.decorators.cache.cache_page()

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

from django.views.decorators.cache import cache_page

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

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

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

urlpatterns = [
    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, будут конкатенированы.

Указание кэша на уровне представления в URL-конфигурации

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

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

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}
Изменено в Django 2.0:

В более ранних версиях не было возможности указать время None кэширования.

Иногда вам может потребоваться кэшировать несколько копий фрагмента в зависимости от динамических данных, которые появляются внутри фрагмента. Например, вы можете захотеть отдельную кэшированную копию боковой панели, используемой в предыдущем примере, для каждого пользователя вашего сайта. Для этого передайте один или несколько дополнительных аргументов, которые могут быть переменными с или без фильтров, в тег {% 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 %}
    {% trans "Welcome to example.com" %}
{% endcache %}

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

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

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

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

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

Указание имени кэша, который не настроен, считается ошибкой.

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

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

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

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

Иногда кэширование всей отрисованной страницы не приносит большой пользы и, на самом деле, является избыточным.

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

Для таких случаев Django предоставляет простой API кэширования низкого уровня. Вы можете использовать этот API для хранения объектов в кэше с любой степенью детализации. Вы можете кэшировать любые объекты Python, которые можно безопасно сериализовать: строки, словари, списки объектов модели и так далее. (Большинство распространённых объектов Python можно сериализовать; см. документацию Python для получения дополнительной информации о сериализации.)

Доступ к кэшу

django.core.cache.caches

Вы можете получить доступ к кэшам, настроенным в параметре CACHES, через объект, похожий на словарь: django.core.cache.caches. Повторные запросы к одному и тому же псевдониму в одном потоке вернут один и тот же объект.

>>> from django.core.cache import caches
>>> cache1 = caches['myalias']
>>> cache2 = caches['myalias']
>>> cache1 is cache2
True

Если указанного ключа не существует, InvalidCacheBackendError будет вызвано.

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

django.core.cache.cache

В качестве сокращения, кэш по умолчанию доступен как django.core.cache.cache:

>>> from django.core.cache import cache

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

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

Базовый интерфейс — set(key, value, timeout) и get(key):

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

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

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

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

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

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

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

>>> cache.get('my_key', 'has expired')
'has expired'

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

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

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

Если вы хотите получить значение ключа или установить значение, если ключ отсутствует в кэше, существует метод get_or_set(). Он принимает те же параметры, что и get(), но по умолчанию устанавливается как новое значение кэша для этого ключа, а не просто возвращается:

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

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

>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)

Также есть интерфейс get_many(), который обращается к кэшу только один раз. get_many() возвращает словарь со всеми запрошенными ключами, которые действительно существуют в кэше (и не истекли):

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Для более эффективного задания нескольких значений используйте set_many() для передачи словаря пар ключ-значение:

>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Как и cache.set(), set_many() принимает необязательный параметр timeout.

В поддерживаемых бэкендах (memcached) set_many() возвращает список ключей, которые не удалось вставить.

Изменено в Django 2.0:

Добавлена возможность возвращать список ключей, которые не удалось вставить.

Вы можете явно удалить ключи с помощью delete(). Это лёгкий способ очистить кэш для конкретного объекта:

>>> cache.delete('a')

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

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

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

>>> cache.clear()

Вы также можете инкрементировать или декрементировать уже существующий ключ, используя методы incr() или decr() соответственно. По умолчанию существующее значение кэша будет инкрементировано или декрементировано на 1. Другие значения инкремента/декремента могут быть указаны, передав аргумент в вызов инкремента/декремента. ValueError будет поднят, если вы попытаетесь инкрементировать или декрементировать несуществующий ключ кэша.:

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

Примечание

Методы incr()/decr() не гарантируют атомарность. В тех бэкендах, которые поддерживают атомарные операции инкремента/декремента (в первую очередь, бэкенд memcached), операции инкремента и декремента будут атомарными. Однако, если бэкенд не предоставляет операции инкремента/декремента напрямую, она будет реализована с помощью двухэтапной операции получения/обновления.

Вы можете закрыть соединение с вашим кэшем с помощью close(), если это реализовано бэкендом кэша.

>>> cache.close()

Примечание

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

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

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

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

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

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

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

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

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

>>> # Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
>>> # Get the default version (assuming version=1)
>>> cache.get('my_key')
None
>>> # Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'

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

>>> # Increment the version of 'my_key'
>>> cache.incr_version('my_key')
>>> # The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
>>> # But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'

Преобразование ключей кэша

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

def make_key(key, key_prefix, version):
    return ':'.join([key_prefix, str(version), key])

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

Настройка кэша KEY_FUNCTION указывает путь к функции с прототипом, соответствующим make_key() выше. При наличии эта пользовательская функция ключа будет использоваться вместо функции объединения ключей по умолчанию.

Предупреждения о ключах кэша

Memcached, наиболее часто используемый бэкенд кэша в производстве, не позволяет ключам кэша иметь длину более 250 символов или содержать пробелы или управляющие символы, и использование таких ключей вызовет исключение. Чтобы поощрять портативный код кэша и минимизировать неприятные сюрпризы, другие встроенные бэкенды кэша выдают предупреждение (django.core.cache.backends.base.CacheKeyWarning) при использовании ключа, который вызовет ошибку в memcached.

Если вы используете бэкенд производства, который может принимать более широкий диапазон ключей (настраиваемый бэкенд или один из встроенных бэкендов, не memcached), и хотите использовать этот более широкий диапазон без предупреждений, вы можете отключить предупреждения CacheKeyWarning с помощью этого кода в модуле management одного из ваших INSTALLED_APPS:

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

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

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

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

…и используйте путь к этому классу в Python в разделе BACKEND вашей настройки CACHES.

Кэши нижнего уровня

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

Вот несколько примеров кэшей нижнего уровня:

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

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

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

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

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

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

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

from django.shortcuts import render
from django.utils.cache import patch_vary_headers

def my_view(request):
    ...
    response = render(request, 'template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

patch_vary_headers принимает экземпляр HttpResponse в качестве первого аргумента и список/кортеж имён заголовков без учёта регистра в качестве второго аргумента.

Подробнее о заголовках Vary см. в официальной спецификации Vary.

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

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

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

Решение заключается в указании того, что кэш страницы должен быть «частным». Для этого в Django используйте декоратор представления cache_control(). Пример:

from django.views.decorators.cache import cache_control

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

Этот декоратор позаботится о передаче соответствующего HTTP-заголовка в фоновом режиме.

Обратите внимание, что параметры управления кэшем «private» и «public» взаимоисключают друг друга. Декоратор гарантирует, что директива «public» удаляется, если необходимо установить «private» (и наоборот). Пример использования этих двух директив — сайт блога, который предлагает как частные, так и общедоступные записи. Общедоступные записи могут быть кэшированы в любом общем кэше. Следующий код использует patch_cache_control(), ручным способом изменения заголовка управления кэшем (он вызывается внутри декоратором cache_control()):

from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie

@vary_on_cookie
def list_blog_entries_view(request):
    if request.user.is_anonymous:
        response = render_only_public_entries()
        patch_cache_control(response, public=True)
    else:
        response = render_private_and_public_entries(request.user)
        patch_cache_control(response, private=True)

    return response

Вы также можете управлять кэшами последующих уровней другими способами (см. RFC 7234 для получения подробной информации об HTTP-кэшировании). Например, даже если вы не используете собственную систему кэширования Django на стороне сервера, вы по-прежнему можете указать клиентам кэшировать представление на определенный период времени с помощью директивы max-age:

from django.views.decorators.cache import cache_control

@cache_control(max_age=3600)
def my_view(request):
    ...

(Если вы используете кэширование посредством промежуточного ПО, оно уже устанавливает max-age со значением настройки CACHE_MIDDLEWARE_SECONDS. В этом случае пользовательский max_age из декоратора cache_control() будет иметь приоритет, а значения заголовков будут объединены корректно.)

Любая допустимая директива ответа Cache-Control допустима в cache_control(). Вот несколько примеров:

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

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

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

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

Порядок MIDDLEWARE

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

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

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

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/topics/cache/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API