Низкоуровневый API кэширования Django

Опубликовал: Monday, February 13, 2024 в категории Django | Пока нет комментариев

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

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

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

В модуле кэширования django.core.cache определен объект cache, который автоматически создается на основе параметра CACHE_BACKEND:

»> from django.core.cache import cache

Основной его интерфейс состоит из двух методов: set(key, value, timeout_ seconds) и get(key):

»> cache. set(‘ my_key’, ‘hello, world!’, 30) »> cache.get(‘my_key’) ‘hello, world!’

Аргумент timeout_seconds необязателен и по умолчанию принимает значение аргумента timeout в параметре CACHE_BACKEND (см. выше).

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

tt Ждем 30 секунд, пока срок хранения ‘my_key’ истечет…

»> cache. get(‘my_key’) None

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

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

»> cache.get(‘my_key’ , ‘has expired’) ‘has expired’

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

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

»> cache.set(‘add_key’, ‘Начальное значение’) »> cache.add(‘add_key’, ‘Новое значение") »> cache, get(‘ add_key’) ‘Начальное значение’

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

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

»> cache, set(‘ а’. 1)

»> cache, set(‘ b’. 2)

»> cache. set(‘ с’, 3)

»> cache. get_many([‘ a’. ‘ b’, ‘с’])

{‘a’: 1, ‘b’: 2, ‘c’: 3}

Наконец, метод delete()пoзвoляeт явно удалять элементы из кэша, например:

»> cache. delete(‘ а’)

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

Промежуточные кэши

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

Приведем несколько примеров промежуточных кэшей:

•      Некоторые страницы может кэшировать ваш интернет-провайдер, так что при запросе страницы сайта http://example.com/ провайдер вернет ее, не обращаясь к самому сайту. Лица, сопровождающие example.com, понятия не имеют о таком кэшировании; провайдер, находящийся между сайтом и броузером, работает прозрачно для сайта.

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

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

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

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

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

Заголовки Vary

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

По умолчанию система кэширования в Django формирует ключи, используя путь, указанный в запросе пути (например, "/stories/2005/ jun/23/bank_robbed/"). Это означает, что в ответ на любой запрос к этому URL будет возвращена одна и та же кэшированная версия страницы вне зависимости от таких различий между броузерами пользователей, как cookie или языковые настройки. Но если содержимое страницы зависит от некоторых заголовков в запросе (cookie, язык, тип броузера), то об этом нужно сообщить механизмам кэширования с помощью заголовка Vary.

Для этого в Django применяется декоратор представления vary_on_ headers:

from django.views.decorators.vary import vary_on_headers

tt Синтаксис Python 2.3. def my_view(request): tt . . .

my_view = vary_on_headers(my_view, ‘User-Agent’)

tt Синтаксис декоратора Python 2.4+. @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): tt …

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

Поскольку варьирование по cookie встречается очень часто, существует специальный декоратор vary_on_cookie. Следующие два представления эквивалентны:

@vary_on_cookie def my_view(request): tt . ..

@vary_on_headers(‘Cookie’)

def my_view(request): «…

Регистр символов в названиях заголовков, передаваемых декоратору

vary_on_headers, не имеет значения: "User-Agent" и "user-agent" эквивалентны.

Можно также напрямую использовать вспомогательную функцию

django.utils.cache.patch_vary_headers. Она устанавливает или добавляет заголовок Vary , например:

from django.utils.cache import patch_vary_headers

def my_view(request): tt . ..

response = render_to_response(‘template_name’, context) patch_vary_headers(response, [‘Cookie’]) return response

Функция patch_vary_headers принимает в качестве первого аргумента объект HttpResponse, а в качестве второго - список или кортеж названий заголовков, имена которых нечувствительны к регистру символов.

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

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

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

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

from django.views.decorators.cache import cache_control

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

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

•     Определить максимальное время нахождения страницы в кэше.

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

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

from django.views.decorators.cache import cache_control

@cache_control(must_revalidate=True, max_age=3600)

def my_view(request): «…

В качестве параметра декоратору cache_control() можно передать любую допустимую протоколом HTTP директиву заголовка Cache-Control. Вот их полный перечень:

•     public=True

•     private=True

•     no_cache=True

•     no_transform=True

•     must_revalidate=True

•     proxy_revalidate=True

•     max_age=num_seconds

•     s_maxage=num_seconds

(Отметим, что дополнительные процессоры всегда добавляют директиву max-age со значением, взятым из параметра настройки САСНЕ_ MIDDLEWARE_SETTINGS. Если декоратору cache_control передать другое значение max-age, то оно будет иметь приоритет.)

Чтобы полностью отменить кэширование, воспользуйтесь декоратором представления never_cache, который добавляет HTTP-заголовки, необходимые, чтобы ответ не кэшировался ни броузером, ни другими кэшами. Например:

from django.views.decorators.cache import never_cache

@never_cache def myview(request): П …

Другие оптимизации

В Django есть и другие дополнительные процессоры, призванные оптимизировать производительность приложения:

•    django.middleware.http.ConditionalGetMiddleware реализует поддержку условных ответов на GET-запросы с помощью заголовков ETag и Last- Modified, реализованную в современных броузерах.

•    django.middleware.gzip.GZipMiddleware сжимает ответы (этот режим поддерживается всеми современными броузерами) для экономии пропускной способности сети и сокращения времени передачи.

Порядок строк в MIDDLEWARE_CLASSES

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

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

•       SessionMiddleware добавляет заголовок Cookie

•       GZipMiddleware добавляет заголовок Accept-Encoding

•       LocaleMiddleware добавляет заголовок Accept-Language

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

django.contrib

Одной из сильных сторон языка Python является его идеология «батарейки входят в комплект»: в состав дистрибутива Python входит большая стандартная библиотека пакетов, которыми можно начинать пользоваться сразу же, не загружая что-то еще. Фреймворк Django также придерживается этой идеологии и поставляется с собственной стандартной библиотекой дополнительных модулей, полезных для решения типичных задач веб-разработки. Их-то мы в этой главе и рассмотрим.

Источник: Головатый А., Каплан-Мосс Дж. Django. Подробное руководство, 2-е издание. - Пер. с англ. - СПб.: Символ- Плюс, 2010. - 560 е., ил.

Похожие посты:

Комментировать

Your email address will not be published. Required fields are marked *