Spec-Zone.ru › Django 2.1

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

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

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

Здесь на помощь приходит кэширование.

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

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 соблюдает метод маршрутизации баз данных (см. ниже).

Как и 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).

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

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

Более старые версии используют стратегию псевдослучайного отбора вместо LRU.

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

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

    Кэши 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',
]

Примечание

Нет, это не опечатка: средний «обновления» должен стоять первым в списке, а «получение» — последним. Подробности немного неясны, но если хотите узнать все подробности, см. Порядок 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 настройки.

Дополнительную информацию о 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, если ваша 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 %}
Изменено в Django 2.0:

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

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

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

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

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)
Новое в Django 2.1.

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 ':'.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."""
        ...

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

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

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

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

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

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

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

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

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

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

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

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

from django.views.decorators.vary import vary_on_headers

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

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

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

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

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

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

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

@vary_on_cookie
def my_view(request):
    ...

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

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

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

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

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

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

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

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

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

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

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

from django.views.decorators.cache import cache_control

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

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

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

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

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

    return response

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

from django.views.decorators.cache import cache_control

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

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

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

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

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

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

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

Порядок MIDDLEWARE

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

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

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

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

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

Spec-Zone.ru

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