Django_-_podrobnoe_rukovodstvo
.pdf320 |
Глава 15. Кэширование |
Django также прекрасно работает с промежуточными кэшами, расположенными выше по тракту прохождения запроса, например Squid (http://www.squid-cache.org/), и с кэшами на стороне броузера. Вы не можете контролировать их напрямую, зато можете отправлять инструкции (в виде HTTP-заголовков) о том, какие части сайта следует кэшировать и как.
Настройка кэша
Настройка системы кэширования не требует значительных усилий.
В частности, ей необходимо сообщить, где должен находиться кэш –
вбазе данных, в файловой системе или непосредственно в памяти. Это важное решение, от которого зависит производительность кэша; понятно, что одни кэши работают быстрее, другие – медленнее.
Выбор механизма кэширования определяется параметром CACHE_BACKEND. Рассмотрим все возможные варианты значений параметра CACHE_BACKEND.
Memcached
Демон Memcached – это самый быстрый и эффективный механизм кэширования из всех поддерживаемых Django, поскольку в этом случае кэш хранится целиком в памяти. Этот механизм первоначально был разработан для высоконагруженного сайта LiveJournal.com, а затем компания Danga Interactive открыла его исходный код. Сейчас он применяется на таких сайтах, как Facebook и Wikipedia, чтобы уменьшить количество обращений к базе данных и резко повысить производительность.
Получить дистрибутив Memcached можно бесплатно на сайте http://danga. com/memcached/. Этот механизм действует как процесс-демон и захватывает строго определенный объем памяти, заданный в конфигурационном файле. Его единственная задача – предоставить быстрый интерфейс для добавления, выборки и удаления из кэша произвольных данных. Все данные хранятся в памяти, поэтому нет никаких издержек, связанных с доступом к базе данных или файловой системе.
После установки самого механизма Memcached понадобится еще установить интерфейс к нему для языка Python. Этот пакет не входит в комплект поставки Django. Существуют две разных версии, поэтому выберите и установите один из следующих модулей:
•• Самым быстрым является модуль cmemcache, который можно получить на сайте http://gijsbert.org/cmemcache/.
•• Если по какой-либо причине вы не можете использовать модуль cmemcache, установите пакет python-memcached, который можно получить по адресу ftp://ftp.tummy.com/pub/python-memcached/. Если этот URL-адрес уже не существует, зайдите на сайт проекта Memcached по адресу http://www.danga.com/memcached/ и загрузите интерфейс для Python из раздела «Client APIs».
Настройка кэша |
321 |
Чтобы воспользоваться механизмом Memcached в Django, установите параметр CACHE_BACKEND в значение memcached://ip:port/, где ip – IP-адрес демона Memcached, а port – номер порта, который этот демон использует для приема запросов.
В следующем примере демон Memcached запущен на сервере localhost (127.0.0.1) и прослушивает порт 11211:
CACHE_BACKEND = ‘memcached://127.0.0.1:11211/’
Одна из замечательных особенностей Memcached – возможность распределять кэш между несколькими серверами. Это означает, что демоны Memcached можно запустить на нескольких компьютерах, и программа будет рассматривать всю группу компьютеров как единый кэш, не дублируя его на каждой машине в отдельности. Чтобы воспользоваться этой возможностью, перечислите все серверы в параметре CACHE_BACKEND через точку с запятой.
В следующем примере кэш распределен между экземплярами Mem cached, запущенными на компьютерах с адресами 172.19.26.240 и 172.19.26.242 (в обоих случаях используется порт 11211):
CACHE_BACKEND = ‘memcached://172.19.26.240:11211;172.19.26.242:11211/’
А в примере ниже кэш распределен между экземплярами Memcached, запущенными на компьютерах с адресами 172.19.26.240 (порт 11211), 172.19.26.242 (порт 11212) и 172.19.26.244 (порт 11213):
CACHE_BACKEND = ‘memcached:// 172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'
Следует также отметить, что кэширование в памяти обладает одним недостатком: в случае выхода сервера из строя весь кэш теряется. Понятно, что оперативная память не предназначена для постоянного хранения данных, поэтому не полагайтесь на кэширование в памяти как на единственный механизм. Без сомнения, ни одну из используемых в Django систем кэширования не следует использовать для длительного хранения; все они предназначены лишь для краткосрочного кэширования, но подчеркнем, что кэширование в памяти особенно уязвимо к сбоям.
Кэширование в базе данных
Чтобы использовать в качестве кэша таблицу в базе данных, сначала создайте эту таблицу следующей командой:
python manage.py createcachetable [cache_table_name]
где [cache_table_name] – произвольное имя таблицы. Эта команда создаст таблицу со структурой, необходимой для работы системы кэширования в базе данных.
322 |
Глава 15. Кэширование |
Затем присвойте параметру CACHE_BACKEND значение “db://tablename”, где tablename – имя только что созданной таблицы. В примере ниже таблица для кэша называется my_cache_table:
CACHE_BACKEND = ‘db://my_cache_table’
Для кэширования используется та же самая база данных, которая указана в файле параметров. Указать какую-то другую базу невозможно.
Для оптимальной работы механизма кэширования в базе необходим быстродействующий сервер базы данных с правильно настроенными индексами.
Кэширование в файловой системе
Для хранения кэша в файловой системе присвойте параметру CACHE_ BACKEND значение “file://”. Например, чтобы кэшированные данные сохранялись в каталоге /var/tmp/django_cache, настройте этот параметр следующим образом:
CACHE_BACKEND = ‘file:///var/tmp/django_cache’
Обратите внимание на три символа слеша в начале. Первые два символа – это часть определения схемы file://, а последний – первый символ пути к каталогу /var/tmp/django_cache. На платформе Windows после префикса file:// следует указать букву диска, например:
file://c:/foo/bar
Пусть к каталогу должен быть абсолютным, то есть должен начинаться от корня файловой системы. Наличие или отсутствие символа слеша в конце пути несущественно.
Не забудьте создать каталог, на который указывает параметр, и сделайте его доступным для чтения и записи пользователю, от имени которого работает веб-сервер. Так, если сервер работает от имени пользователя apache, то каталог /var/tmp/django_cache должен существовать и быть доступен пользователю apache для чтения и записи.
Каждый кэшированный блок сохраняется в отдельном файле в формате сериализации, который определяется Python-модулем pickle. Имена файлов – это ключи кэша, экранированные с учетом соглашений файловой системы.
Кэширование в локальной памяти
Если вас привлекают преимущества кэширования в памяти, но нет возможности запустить Memcached, подумайте о кэшировании в локальной памяти. Это многопроцессный и безопасный относительно потоков выполнения механизм. Настраивается он путем определения в парамет ре CACHE_BACKEND значения “locmem:///”, например:
CACHE_BACKEND = ‘locmem:///’
Настройка кэша |
323 |
Отметим, что у каждого процесса будет свой собственный кэш, то есть таким способом невозможно будет организовать общий кэш для нескольких процессов. Отсюда с очевидностью следует, что этот механизм неэффективно расходует оперативную память, поэтому для промышленной эксплуатации он, скорее всего, не подходит. Но для разработки в самый раз.
Фиктивное кэширование (для разработки)
Наконец, в комплект поставки Django входит реализация «фиктивного» кэша, который на самом деле ничего не кэширует, а лишь реализует интерфейс кэша.
Его удобно использовать, когда имеется действующий сайт, на котором в разных местах активно применяется кэширование, и среда для разработки и тестирования, в которой вы ничего не хотите кэшировать и вообще не хотите вносить в код модификации специально для этого. Чтобы включить фиктивный кэш, определите параметр CACHE_BACKEND следующим образом:
CACHE_BACKEND = ‘dummy:///’
Пользовательский механизм кэширования
Хотя Django поддерживает целый ряд готовых систем кэширования, иногда возникает необходимость задействовать специализированный механизм. Чтобы использовать внешнюю систему кэширования, определите в параметре CACHE_BACKEND путь импорта Python в качестве схемы URI (часть, предшествующая двоеточию), например:
CACHE_BACKEND = ‘path.to.backend://’
При создании собственного механизма кэширования можно взять за образец реализации стандартных систем. Соответствующий исходный код находится в каталоге django/core/cache/backends/.
Примечание
При отсутствии достаточно веских причин (например, сервер не поддерживает стандартные механизмы) мы рекомендуем пользоваться системами кэширования, входящими в дистрибутив Django. Они хорошо протестированы и просты в употреблении.
Аргументы параметра CACHE_BACKEND
Все подсистемы кэширования могут иметь дополнительные настройки, которые определяются с помощью необязательных аргументов. Они задаются в формате строки запроса в параметре CACHE_BACKEND. Допустимыми являются следующие аргументы:
324 |
Глава 15. Кэширование |
•• timeout: время хранения в кэше в секундах. Значение по умолчанию равно 300 секундам (5 минутам).
•• max_entries: для механизмов locmem, filesystem и database определяет максимальное количество элементов в кэше, по достижении которого старые значения начинают удаляться. Значение по умолчанию равно 300.
•• cull_percentage: доля элементов кэша, удаляемых при достижении порога max_entries. Вычисляется как 1/cull_percentage, то есть при cull_percentage=2 будет удалена половина элементов кэша.
Значение 0 означает, что при достижении порога max_entries следует очистить кэш целиком. В этом случае очистка производится гораздо быстрее, но ценой потери данных в кэше.
Вследующем примере определяется параметр timeout со значением 60:
CACHE_BACKEND = “memcached://127.0.0.1:11211/?timeout=60”
Аздесь определяется параметр timeout со значением 30 и параметр max_ entries со значением 400:
CACHE_BACKEND = “locmem:///?timeout=30&max_entries=400”
Неизвестные аргументы и недопустимые значения известных аргументов будут просто игнорироваться.
Кэширование на уровне сайта
Проще всего кэшировать сайт целиком. Для этого в параметр
MIDDLEWARE_CLASSES необходимо добавить строки ‘django.middleware.cache. UpdateCacheMiddleware’и‘django.middleware.cache.FetchFromCacheMiddleware’, например:
MIDDLEWARE_CLASSES = ( ‘django.middleware.cache.UpdateCacheMiddleware’, ‘django.middleware.common.CommonMiddleware’, ‘django.middleware.cache.FetchFromCacheMiddleware’,
)
Примечание
Это не опечатка: класс «update» должен быть первым, а класс «fetch» последним. Причина нетривиальна, интересующимся рекомендуем прочитать раздел «Порядок строк в MIDDLEWARE_CLASSES» в конце главы.
Затем включите в файл с настройками следующие обязательные параметры:
•• CACHE_MIDDLEWARE_SECONDS: сколько секунд страница должна храниться в кэше.
Кэширование на уровне представлений |
325 |
•• CACHE_MIDDLEWARE_KEY_PREFIX: если на одном сервере под управлением одной инсталляции Django работает несколько сайтов с общим кэшем, то укажите в этом параметре имя сайта или еще какую-нибудь строку, уникальную для данного экземпляра Django, чтобы предотвратить коллизии ключей. Если это в вашем случае не важно, задайте пустую строку.
Кэшированию подвергаются все страницы, для которых в запросе GET или POST нет параметров. Если при этом необязательный параметр CACHE_ MIDDLEWARE_ANONYMOUS_ONLY имеет значение True, то кэшируются только запросы от анонимных посетителей. Это простой и эффективный способ отключить кэширование персонализированных страниц (в том числе страниц административного интерфейса). Отметим, что при использовании параметра CACHE_MIDDLEWARE_ANONYMOUS_ONLY необходимо активировать также дополнительный процессор AuthenticationMiddleware.
Кроме того, дополнительные процессоры кэширования автоматически включают в каждый объект HttpResponse следующие заголовки:
•• Заголовок Last-Modified, содержащий дату и время запроса актуальной (не кэшированной) версии страницы.
•• Заголовок Expires, содержащий текущие дату и время плюс значение параметра CACHE_MIDDLEWARE_SECONDS.
•• Заголовок Cache-Control, содержащий максимальный срок хранения страницы, также вычисляемый на основе параметра CACHE_ MIDDLEWARE_SECONDS.
Примечание
О дополнительных процессорах см. главу 17.
Если некоторое представление самостоятельно устанавливает срок хранения (то есть значение параметра max-age в заголовке Cache-Control), то страница кэшируется на этот срок, а не на CACHE_MIDDLEWARE_SECONDS секунд. Декораторы из модуля django.views.decorators.cache позволяют без труда изменить срок хранения (декоратор cache_control) или вообще отключить кэширование представления (декоратор never_cache). Дополнительные сведения об этих декораторах см. в разделе «Управление кэшем: другие заголовки».
Кэширование на уровне представлений
Имеется возможность более точно управлять кэшированием – на уровне отдельных представлений. В модуле django.views.decorators.cache определен декоратор cache_page, который автоматически кэширует результат работы представления, например:
from django.views.decorators.cache import cache_page
326 |
Глава 15. Кэширование |
def my_view(request):
# ...
my_view = cache_page(my_view, 60 * 15)
Или, применяя синтаксис декораторов в версии Python 2.4 и выше:
@cache_page(60 * 15) def my_view(request):
# ...
Декоратор cache_page принимает единственный аргумент: время хранения в кэше в секундах. В примере выше результат работы представления my_view() будет храниться в кэше 15 минут. (Отметим, что мы написали 60 * 15, чтобы было понятнее, то есть 15 раз по 60 секунд, итого 900 секунд.)
Элементы кэша на уровне представлений, как и на уровне сайта, индексируются значением URL. Если несколько URL указывают на одно и то же представление, то для каждого URL в кэше будет храниться отдельная версия. Так, если имеется такая конфигурация URL:
urlpatterns = (‘’, (r’^foo/(\d{1,2})/$’, my_view),
)
то запросы к /foo/1/ и /foo/23/ будут кэшироваться порознь, как и следовало ожидать. Но после запроса к конкретному URL (например, /foo/23/) все последующие запросы к этому же URL будут удовлетворяться из кэша.
Настройка кэширования на уровне представлений в конфигурации URL
В примерах из предыдущего раздела настройка кэширования на уровне представлений выполняется непосредственно в программном коде, за счет использования декоратора cache_page перед функцией my_view. При таком подходе образуется связь между представлением и системой кэширования, что нежелательно по нескольким причинам. Например, что если вы захотите воспользоваться теми же самыми функциями представления на другом сайте, где кэширование отключено? Или передать их другим разработчикам, которым кэширование не нужно? Для решения этих проблем режим кэширования на уровне представлений следует задавать в конфигурации URL, а не в самих функциях.
Это нетрудно, достаточно обернуть функцию представления функцией cache_page в ссылке, указанной в шаблоне URL. Тогда прежняя конфигурация URL
urlpatterns = (‘’, (r’^foo/(\d{1,2})/$’, my_view),
)
Кэширование фрагментов шаблона |
327 |
примет вид:
from django.views.decorators.cache import cache_page
urlpatterns = (‘’,
(r’^foo/(\d{1,2})/$’, cache_page(my_view, 60 * 15)),
)
Не забудьте только импортировать cache_page в конфигурацию URL.
Кэширование фрагментов шаблона
Если необходим еще более точный контроль над кэшированием, можно воспользоваться тегом cache и с его помощью организовать кэширование фрагментов шаблона. Чтобы открыть шаблону доступ к этому тегу, поместите в начале шаблона директиву {% load cache %}.
Шаблонный тег {% cache %} кэширует содержимое блока на указанное время. Он принимает по меньшей мере два аргумента: время хранения в кэше в секундах и имя фрагмента. Например:
{% load cache %}
{% cache 500 sidebar %}
.. боковая панель .. {% endcache %}
Иногда бывает необходимо кэшировать несколько вариантов фрагмента в зависимости от динамических данных внутри него. Например, в предыдущем примере содержимое боковой панели может отличаться для каждого посетителя сайта. В этом случае нужно передать шаб лонному тегу {% cache %} дополнительные аргументы, уникально идентифицирующие фрагмент:
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. боковая панель для аутентифицированного пользователя .. {% endcache %}
Ничто не мешает задавать несколько аргументов для идентификации фрагмента. Для этого нужно лишь передать тегу {% cache %} столько аргументов, сколько необходимо.
Время хранения в кэше может быть шаблонной переменной; главное, чтобы ее значением было целое число. Так, если допустить, что шаб лонная переменная my_timeout имеет значение 600, то следующие два примера будут эквивалентны:
{% |
cache |
600 sidebar %} ... |
{% |
endcache %} |
{% |
cache |
my_timeout sidebar %} |
... {% endcache %} |
|
Такой подход позволит избежать повторения в шаблонах. Срок хранения можно определить в одном месте, в некоторой переменной, а затем передавать эту переменную разным шаблонам.
328 |
Глава 15. Кэширование |
Низкоуровневый API кэширования
Иногда кэширование страницы целиком не дает существенного выигрыша и даже оказывается чрезмерным и неудобным.
Например, сайт может включать представление, выполняющее несколько дорогостоящих запросов, результаты которых изменяются с разной периодичностью. В таком случае не имеет смысла кэшировать страницу целиком, как предлагает стратегия кэширования на уровне представления или на уровне сайта, так как ни к чему сохранять в кэше все результаты (поскольку некоторые данные часто изменяются), но желательно кэшировать лишь редко изменяющиеся результаты.
Для таких ситуаций 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:
# Ждем 30 секунд, пока срок хранения ‘my_key’ истечет...
>>> cache.get(‘my_key’) None
Мы не рекомендуем хранить в кэше литеральное значение None, потому что невозможно различить два случая: возврат кэшированного значения None и отсутствие значения в кэше, обозначаемое возвратом None.
cache.get() может принимать аргумент default. Он определяет, какое значение следует вернуть, если объект отсутствует в кэше:
>>> cache.get(‘my_key’, ‘has expired’) ‘has expired’
Чтобы добавить в кэш новый ключ (при условии, что он еще не существует), применяется метод add(). Он принимает те же параметры, что
Низкоуровневый API кэширования |
329 |
и метод set(), но не пытается обновить кэш в случае, когда указанный ключ уже имеется:
>>>cache.set(‘add_key’, ‘Начальное значение’)
>>>cache.add(‘add_key’, ‘Новое значение’)
>>>cache.get(‘add_key’)
‘Начальное значение’
Чтобы узнать, поместил ли метод add() новое значение в кэш, можно проверить код возврата. Метод возвращает True, если значение сохранено, и False в противном случае.
Винтерфейсе определен также метод 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}
Наконец, метод delete()позволяет явно удалять элементы из кэша, например:
>>> cache.delete(‘a’)
Можно также увеличить или уменьшить значение хранящегося в кэше ключа. Для этого служат методы 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), эти операции будут выполняться атомарно. Но если используемая система кэширования не поддерживает такой механизм, то инкремент и декремент реализуются в виде последовательности двух операций: чтение и обновление.
