Spec-Zone.ru › Django 3.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 вам потребуется установить привязку 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 может хранить кэшированные данные в вашей базе данных. Это наилучшим образом работает, если у вас есть быстрый и хорошо индексированный сервер базы данных.

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

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

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

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

Кэширование «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.middleware.cache.UpdateCacheMiddleware' и 'django.middleware.cache.FetchFromCacheMiddleware' в настройки MIDDLEWARE, как показано в этом примере:

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

Примечание

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

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

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

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

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

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

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

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

urlpatterns = [
    path('foo/<int:code>/', my_view),
]

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

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

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

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

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

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

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

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

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

urlpatterns = [
    path('foo/<int:code>/', my_view),
]

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

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]

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

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

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

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

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

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

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

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

{% get_current_language as LANGUAGE_CODE %}

{% cache 600 welcome LANGUAGE_CODE %}
    {% 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'].

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

cache.delete(key, version=None)

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

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

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

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

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

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

cache.touch() устанавливает новый срок действия для ключа. Например, чтобы обновить ключ, который должен истечь через 10 секунд:

>>> cache.touch('a', 10)
True

Как и в других методах, аргумент timeout является необязательным и по умолчанию имеет значение TIMEOUT опции соответствующего бэкенда в настройке CACHES.

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

cache.incr(key, delta=1, version=None)
cache.decr(key, delta=1, version=None)

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

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

Примечание

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

cache.close()

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

>>> cache.close()

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

def make_key(key, key_prefix, version):
    return '%s:%s:%s' % (key_prefix, version, key)

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

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

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

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

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

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

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

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

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

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

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

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

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

Это указывает кэшам нижнего уровня изменять кэширование и по тому и по другому, что означает, что каждая комбинация user-agent и cookie получит собственное значение кэша. Например, запрос с user-agent Mozilla и значением cookie foo=bar будет считаться отличным от запроса с user-agent 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-заголовка за кулисами.

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

Spec-Zone.ru

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