Кэширование играет одну из основных ролей в быстродействии сайтов и сравнительно просто настраивается на стороне сервера. Веб-разработчики часто сталкиваются с кэшированием, ибо браузеры и проксирующие серверы, пытаясь ускорить работу сайтов для пользователя, очень часто стараются сохранить у себя максимально большое количество документов в локальном кэше.
При открытии страницы сайта в браузере (если эта страница была не первой на нем или если вы уже заходили на этот сайт в недалеком прошлом) браузер возьмет все (или почти все) ресурсы, необходимые для отображения страницы из кэша, чем весьма сильно может ускорить загрузку страницы. Однако при этом может потеряться актуальность представляемых данных, поэтому политика кэширования во всех браузерах реализована немного по-разному: каждый из них по-своему добивается компромисса между целостностью и актуальностью данных.
В качестве базовой настройки обычно используется инструкция браузеру от сервера для бессрочного кэширования каждого URL. Чтобы дать серверу понять, что файл был изменен, требуется использовать другое имя файла. На больших веб-сайтах обычно устанавливается такой процесс изменения этих файлов, что номер каждой новой версии добавляется к имени файла (например, common.v1.css становится common.v2.css). Соответственно, ссылки на эти файлы тоже должны быть программно обновлены, и это не зависит от языка программирования или системы шаблонов.
Заголовок Expires является частью спецификации HTTP 1.0. Когда HTTP-сервер отправляет ресурс (например, HTML-страницу или изображение) браузеру, он может дополнительно с ответом отправить этот заголовок с меткой времени. Браузеры обычно хранят ресурс вместе с информацией об истечении его срока действия в локальном кэше. При последующих пользовательских запросах к одному и тому же ресурсу браузер может сравнить текущее время и метку времени у закэшированного ресурса. Если метка времени указывает на дату в будущем, то браузер может просто загрузить ресурс из кэша, вместо того чтобы запрашивать его с сервера.
Даже если дата истечения срока действия ресурса находится в будущем, браузеры (включая Internet Explorer 4.0) по-прежнему выполнят дополнительный GET-запрос на сервер для определения, является ли закэшированная версия ресурса такой же, как на сервере. После тщательного анализа было установлено, что издержки на дополнительный запрос не являются ни оптимальными, ни необходимыми. По этой причине поведение всех браузеров было изменено следующим образом: если окончание срока действия закэшированного ресурса позже, чем время запроса, то браузер загрузит ресурс напрямую из кэша без запросов к серверу. Сайты, которые используют заголовок Expires для часто посещаемых, но редко обновляемых страниц, получат ощутимый выигрыш в трафике, а у пользователей быстрее отобразятся страницы.
Кэширование можно оптимизировать и дальше путем настройки заголовков, которые сервер отправляет при запросе требуемых ресурсов. Вместо того чтобы отдавать его содержание, сервер может отправить заголовки, что у клиента уже имеется закэшированная версия файла, для которого установлено, что срок его хранения истечет в будущем. Картинки обычно так часто не обновляют, зачем же их запрашивать снова и снова? Подробнее этот аспект будет рассмотрен в следующем разделе.
Установка и Cache-Control, и Expires должна обеспечить поддержку протоколов HTTP 1.0 и 1.1. Типичный ответ сервера, урезанный только до заголовков с кэширующей информацией, может выглядеть следующим образом:
Date: Tue, 17 Apr 2008 18:39:57 GMT
Cache-Control: max-age=315360000
Expires: Fri, 14 Apr 2018 18:39:57 GMT
Last-Modified: Mon, 16 Apr 2008 23:39:48 GMT
В спецификации RFC-2616 HTTP-кэшированию посвящена целая глава. В ней подробно рассматривается, что означают отдельные заголовки. Давайте остановимся на ключевых моментах.
Заголовок Expires устанавливает время актуальности информации. Для ресурсов, которые не должны кэшироваться, его нужно выставлять в текущий момент (документ устаревает сразу же после получения), для форсирования кэширования его можно определять на достаточно далекую дату в будущем, например:
Expires: Mon, 13 Oct 2019 00:00:00 GMT
Cache-Control определяет набор директив, относящихся непосредственно ко времени и специфике кэширования документа. Для запрета кэширования можно выставить его следующим образом:
Cache-Control: no-store, no-cache, must-revalidate
Если же мы, наоборот, хотим положить ресурс в кэш браузера на достаточно продолжительный период времени, то стоит воспользоваться такой конструкцией:
Cache-Control: max-age=31536000
в данном случае срок кэширования примерно равен году (60 * 60 *24 * 365 секунд).
Директива Pragma: no-cache используется для протокола HTTP/1.0. На данный момент можно с полным правом считать ее устаревшей конструкцией. Однако, для корректного запрета кэширования стоит все же выставлять и этот заголовок — никогда не знаешь наверняка, какой еще пользовательский агент обратится к серверу и какой протокол он будет использовать. Например, wget просто не поддерживает HTTP/1.1 (из-за Content-Encoding: chunked).
Запретить кэширование можно и прямо из конфигурации Apache (подробная конфигурация для оптимальной производительности приводится в восьмой главе). Для этого нам нужны следующие строки:
# Проверяем, что подключен mod_headers
# Тогда выставляем заголовок Cache-Control
Header append Cache-Control "no-store, no-cache, must-revalidate"
Header append Pragma "no-cache"
# Теперь проверяем наличие mod_expires и активируем этот модуль для заголовка
# Expires
ExpiresActive On
ExpiresDefault "now"
При запрете кэширования мы заставим браузер каждый раз заново загружать документы и ресурсные файлы. В последнем случае это совсем не оптимально и может привести к заметному замедлению работы с сайтом. Давайте рассмотрим, как можно выставить срок действия кэша на достаточно продолжительное время.
# Разрешим кэширование на 1 год, проверив наличие mod_expires
# Apache сам позаботится о выставлении корректного max-age
ExpiresActive On
ExpiresDefault "access plus 1 year"
В итоге мы запретили браузерам загружать статические компоненты с сайта, чем заметно увеличили его производительность. Однако что же нам делать, если мы все же решим изменить исходный ресурсный файл?
Если мы устанавливаем время кэширования на несколько лет (фактически на бесконечность), то нам нужно каким-то образом сообщить клиентскому браузеру, что исходный ресурс-то у нас поменялся: иначе браузер его никогда повторно не запросит. Что для этого нужно?
Вообще говоря, для того, чтобы сообщить об обновлении файла в таком случае, нужно изменить его адрес — т. е. заявить обновленный файл под другим URL, что и будет гарантировать его обновление в локальном кэше. Однако это можно сделать двумя способами. Во-первых, мы можем в конце файла обновить GET-строку запроса, например, используя номер версии
http://webo.in/a.css?v23
или дату последнего изменения
http://webo.in/a.css?20081010
Оба этих способа изменяют адрес ресурса (в данном случае, это файл стилей), поэтому браузер обязан его запросить.
Во-вторых, мы можем номер версии добавить в сам файл
http://webo.in/a.v23.css
чтобы исключить возможные проблемы с локальными проксирующими серверами, которые могут не кэшировать у себя файлы с GET-параметрами. В этом случае (дабы не плодить новые физические файлы) нам нужно прописать в конфигурации сервера (например, Apache), чтобы при запросах такого вида отдавался каждый раз один и тот же физический файл. Это можно сделать примерно следующим образом (справедливо для CSS- и JavaScript-файлов):
RewriteRule ^(.*)\.(v[0-9]+)?\.(css|js)$ $1.$2 [QSA,L]
Таким образом, вместо файла a.v23.css будет отдаваться a.css.
Если текущая конфигурация позволяет использовать последний вариант, то стоит остановиться на нем. Иначе сброс кэша придется осуществлять через обновления GET-параметров исходного файла.
«Пробивка» вечного кэширования с помощью подмены директории несколько лучше, чем использование GET-переменной. В качестве основного аргумента можно привести следующее рассуждение: если хоть где-то в цепочке от сервера до браузера есть кэширующий прокси, то по умолчанию он сочтёт запрос с «?» динамическим и отправит запрос на сервер, не пытаясь его искать у себя в локальном кэше. Браузер, разумеется, будет ждать в этом случае несколько больше.
В каком-то смысле это будет экономия «на спичках», так как эффект будет заметен только при посещении сайта другим пользователем той же локальной сети, к примеру. Но тем не менее эффект есть, и процент запросов с заголовком X-Forwarded-For достаточно велик.
При разработке веб-сайта частота изменения страниц сильно колеблется. Некоторые страницы будут меняться ежедневно, некоторые останутся одними и теми же с самого момента своего создания. Для того чтобы позволить сайту регулировать частоту, с которой браузер должен запрашивать HTTP-сервер об изменениях в ресурсе, в Internet Explorer 5 было введено 2 расширения HTTP-заголовка Cache-Control: pre-check и post-check. К сожалению, другие браузеры не поддержали инициативу, поэтому эти директивы на данный момент действительны только для IE.
Вводя эти расширения, Internet Explorer уменьшает сетевой трафик, так как отправляет меньше запросов к серверу. Дополнительно при этом улучшается пользовательское восприятие, когда браузер отображает ресурсы из кэша и проверяет обновления в фоновом режиме после специального интервала.
Расширения post-check и pre-check для Cache-Control определены следующим образом.
post-check
Определяет интервал времени в секундах, после которого ресурс должен быть проверен на актуальность. Эта проверка может быть выполнена и после того, как пользователь загрузит страницу из кэша, но при следующей загрузке он обязательно должен получить обновленную версию.
pre-check
Определяет интервал времени в секундах, после которого проверка актуальности ресурса должна быть произведена перед его отображением для пользователя.
Когда к браузеру поступает запрос на открытие ресурса, который находится в кэше, и при этом кэш содержит расширения Cache-Control (отправленные с сервера как часть заголовка HTTP-ответа), тогда IE использует эти расширения и логика для получения последней версии страницы с сервера будет следующей:
Если еще не закончился интервал времени post-check, то просто отобразим страницу из кэша.
Если с момента последнего запроса страницы прошло время, лежащее между интервалами post-check и pre-check, то отобразим страницу из кэша. При этом в фоновом режиме запросим HTTP-сервер на предмет того, изменялась ли страница с момента последнего запроса браузером. Если страница изменялась, то запросим ее и сохраним в кэше. При этом в браузере ничего не поменяется: у него будет более старая версия, полученная изначально из кэша, однако ее загрузка произойдет максимально быстро.
Если уже истекло время, отмеченное интервалом pre-check, то при запросе страницы пользователем сначала спросим у HTTP-сервера, изменилась ли страница со времени ее последней загрузки браузером. Если страницы изменилась, загрузим ее и отобразим обновленную версию. Если страница не изменилась, то ее кэш и расширения Cache-Control в любом случае будут обновлены.
Заметим, что кнопка «Обновить» (включая клавишу F5) не запускает данный механизм, потому что «Обновить» всегда отправляет на сервер запрос If-Modified-Since. Однако с помощью описанной выше логики будут открываться все ссылки.
В следующем примере сервер уведомляет Internet Explorer, что содержание документа не будет меняться в течение 1 часа (pre-check=3600) и что его можно загружать прямо из локального кэша. В случае же изменения страницы, если пользователь запросит ее по истечении 15 минут, Internet Explorer должен отобразить локальный кэш, но при этом в фоновом режиме проверить, является ли сохраненная копия страницы актуальной, и по необходимости загрузить ее с сервера.
Cache-Control: post-check=900,pre-check=3600
Использование описанных параметров для тонкой настройки общения сервера с IE может оказаться весьма полезным для высоконагруженных проектов, ориентированных на пользователей Internet Explorer. Это позволит как существенно уменьшить число запросов к серверу и сэкономить его ресурсы, так и поддерживать актуальность кэшируемых документов.
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Dec 2008 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"
Content-Length: 19145
GET /b.png HTTP/1.1
Host: i.webo.in
If-Modified-Since: Tue, 12 Dec 2008 03:03:59 GMT
If-None-Match: "10c24bc-4ab-457e1c1f"
HTTP/1.1 304 Not Modified
FileETag MTime Size
FileETag none
На MacWorld’2008 Steve Jobs анонсировал, что Apple уже продала на текущий момент 4 миллиона iPhone, что составляет по 20 тысяч iPhone каждый день. В докладе Net Applications говорится, что общая доля пользователей Интернета с iPhone поднялась до 0,12% в декабре 2007 года, обогнав, в совокупности, все браузеры для мобильных устройств, работающие под управлением Windows. iPhone от Apple изменил ситуацию для пользователей, выходящих в Интернет через мобильные устройства.
Веб-разработчики теперь могут создавать функциональные и привлекательные приложения, которые работают на iPhone-версии браузера Safari для мобильных устройств. Однако при этом сам iPhone не только предоставляет новые и достаточно интересные возможности для разработчиков мобильных устройств, но и обнаруживает ряд уникальных проблем в сфере производительности.
По поводу этого устройства на данный момент доступна только ограниченная информация, а понимание основ его алгоритмов кэширования является существенным для создания высокопроизводительного сайта. Команда по исключительной производительности Yahoo! рассмотрела кэширующие свойства iPhone. Основной акцент делался на исследовании следующих вещей:
максимальный размер кэша для каждого компонента по отдельности;
максимальный размер кэша для всех компонентов;
эффект от gzip-сжатия для максимального размера кэша;
остаются ли компоненты в кэше после перезагрузки iPhone.
Результаты экспериментов над кэшом как для Apple iPhone, так и для iPod Touch получились одинаковые.
В результате исследования было установлено, что если размер компонентов превышает 25 Кб, то браузер в iPhone не кэширует этот компонент. Поэтому веб-страницы, которые спроектированы специально для iPhone, должны уменьшить размер компонентов не более чем до 25 Кб, чтобы оптимизировать кэширующее поведение.
Хорошие новости заключаются в том, что если браузер загружает новый компонент, размер которого больше чем 25 Кб, то это не влияет на компоненты, которые уже находятся в кэше. Закэшированные компоненты заменяются более новыми, только если последние не превосходят 25 Кб. При этом выбирается самый маленький из последних используемых.
На www.apple.com указано, что существует предел в 10 Мб для индивидуальных компонентов. Этот предел зависит от способностей браузера хранить файлы в оперативной памяти (не на диске). Однако реальный максимальный размер файлов, которые iPhone может обрабатывать, значительно меньше. Он зависит от текущей фрагментации памяти и других приложений, которые запущены параллельно с браузером. Компоненты, которые не поместились в кэш, запрашиваются браузером снова после закрытия текущей страницы.
Было также установлено, что максимальный размер кэша для нескольких компонентов (или целой страницы со всеми необходимыми ей ресурсами) составляет 475-500 Кб.
Было проанализировано, какое влияние на кэш оказывает передача компонентов в обычном или сжатом виде. Предел кэша в 25 Кб на компонент не зависит от того, был ли он передан в архивированном виде. Safari в iPhone декодирует компонент до того, как он сохранится в кэше. Таким образом, значение имеет только несжатый размер файлов, что еще больше подчеркивает важность минимизации всех компонентов.
Однако размер все же имеет значение, потому что влияет на время передачи по сети. Поэтому рекомендуется разбивать все ресурсы страницы на файлы по 25 Кб, а затем уже применять к ним сжатие.
Иногда случается, что пользователям iPhone или iPod Touch нужно перезагрузить систему, или, другими словами, выключить устройство и загрузить его заново. Для этого нужно удерживать кнопку sleep в течение пяти секунд, потом просмотреть небольшую заставку при выключении. Предположим, что пользователь просматривал ваш сайт как раз перед тем, как перезагрузиться. Сохранятся ли картинки и таблицы стилей в кэше браузера, ускорив загрузку вашего сайта, когда пользователь на него вернется?
В результате исследования было получено, что кэш браузера в iPhone не сохраняется после перезагрузки. Это означает, что кэш в Safari для iPhone получает часть системной памяти для создания там кэшированных версий компонентов, однако не сохраняет их в постоянном месте.
В случае реальной необходимости сайты можно и нужно проектировать специально для iPhone. Кроме повышения удобства использования стоит также обратить внимание на уменьшение общего размера страницы и улучшение клиентской производительности. В данном случае нам нужно ограничить размер каждого из компонентов страницы 25 Кб для оптимизации кэширующего поведения.
Заданная ограниченная скорость сетевого беспроводного соединения в iPhone наряду с очисткой кэша в браузере при перезагрузке выводят на первый план важность уменьшения числа HTTP-запросов для повышения производительности. И это становится даже более важным, чем в случае загрузки страницы в обычном браузере. Подробнее об уменьшении числа запросов к серверу — техниках объединения файлов, использовании CSS Sprites и data:URI — рассказывается в следующей главе.