Spec-Zone.ru › Django 1.9

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

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

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

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

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

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

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

python manage.py createcachetable

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Недопустимые аргументы игнорируются, как и недопустимые значения известных аргументов.

Кэш на сайт

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

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

Примечание

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

Затем добавьте следующие необходимые настройки в файл настроек 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:

  • Устанавливает заголовок Last-Modified на текущую дату/время при запросе свежей (не кэшированной) версии страницы.
  • Устанавливает заголовок Expires на текущую дату/время плюс определенное значение CACHE_MIDDLEWARE_SECONDS.
  • Устанавливает заголовок Cache-Control , чтобы указать максимальный срок хранения страницы — снова, из настройки CACHE_MIDDLEWARE_SECONDS.

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

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

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

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

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

django.views.decorators.cache.cache_page()

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

from django.views.decorators.cache import cache_page

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

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

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

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', my_view),
]

то запросы на /foo/1/ и /foo/23/ будут кэшироваться отдельно, как вы, вероятно, ожидаете. Но как только определенный URL (например, /foo/23/) был запрошен, последующие запросы на этот URL будут использовать кэш.

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

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

Вы также можете переопределить префикс кэша для каждого представления. cache_page принимает необязательный ключевой аргумент, key_prefix, который работает так же, как и параметр CACHE_MIDDLEWARE_KEY_PREFIX для middleware. Его можно использовать так:

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

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

Указание кэша для каждого представления в URLconf

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

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

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', my_view),
]

Вот то же самое, с my_view обернутым в cache_page:

from django.views.decorators.cache import cache_page

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]

Кэширование фрагментов шаблонов

Если вам нужен ещё больший контроль, вы также можете кэшировать фрагменты шаблонов с помощью тега шаблона cache. Чтобы предоставить вашему шаблону доступ к этому тегу, поместите {% load cache %} в верхней части вашего шаблона.

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

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

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

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

Совершенно нормально указывать более одного аргумента для идентификации фрагмента. Просто передайте столько аргументов тегу {% cache %}, сколько вам нужно.

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

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

{% get_current_language as LANGUAGE_CODE %}

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

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

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

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

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

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

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

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

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

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

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

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

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

Для таких случаев Django предоставляет простой API кэша низкого уровня. Вы можете использовать этот API для хранения объектов в кэше с любым желаемым уровнем детализации. Вы можете кэшировать любой объект Python, который можно безопасно закодировать с помощью pickling: строки, словари, списки объектов модели и так далее. (Большинство обычных объектов Python могут быть закодированы с помощью pickling; обратитесь к документации Python для получения дополнительной информации о 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'].

Основные возможности использования

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

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

Аргумент 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'

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

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

Метод get_or_set() был добавлен.

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

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

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

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

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

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

>>> cache.delete('a')

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

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

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

>>> cache.clear()

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

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

Примечание

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

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

>>> cache.close()

Примечание

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

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

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

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

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

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

from django.views.decorators.vary import vary_on_headers

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

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

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

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

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

Это указывает кэшам нижнего уровня на необходимость вариативности по обоим параметрам, что означает, что каждая комбинация пользовательского агента и cookie получит своё собственное значение кэша. Например, запрос с пользовательским агентом Mozilla и значением cookie foo=bar будет считаться разным от запроса с пользовательским агентом Mozilla и значением cookie foo=ham.

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

@vary_on_cookie
def my_view(request):
    ...

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

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

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

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

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

patch_vary_headers принимает экземпляр HttpResponse в качестве первого аргумента и список/кортеж имён заголовков (нечувствительных к регистру) как второй аргумент.

Дополнительную информацию о заголовках Vary см. в официальном спецификации Vary.

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

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

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

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

from django.views.decorators.cache import cache_control

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

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

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

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

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

    return response

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

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

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

Порядок MIDDLEWARE_CLASSES

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

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

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

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

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

Spec-Zone.ru

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