Оптимизируем время загрузки страницы

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

Время загрузки HTML

Как видно из таблицы, время загрузки html, в основном, составляет лишь малую часть от общего времени загрузки/инициализации страницы у конечного пользователя. Давайте рассмотрим подробнее, из чего же это время складывается. Это сетевые задержки (ожидание ответа от DNS, время на установление соединения с сервером), это время, уходящее на загрузку ресурсов (картинок, анимации) и загрузку файлов стилей и скриптов (CSS и JavaSscript).

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

Как можно уменьшить сетевые задержки? С одной стороны, увеличения числа серверов, которые одновременно могут отдавать ресурсы страницы, должно уменьшить общее время загрузки. С другой стороны, уходит определенное время на установление соединения с каждым сервером (обычно DNS lookup занимает 20-120 миллисекунд). Исходя из практических данных, лучшим решением будет использование не более 4 хостов на страницу вкупе с заголовком Keep-alive, который позволит загружать все запрашиваемые файлы за одно соединение. Естественно, при этом стоит избегать редиректов. Если адрес ресурса изменился, лучше его изменить на самой странице, а не заставлять пользователей совершать дополнительные запросы к серверу.

Размер заголовка Cookie

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

Как можно уменьшить размер ответа? Тут простор действий гораздо больше. Наиболее известным методов является minify (исключение всего ненужного из кода, подробнее в докладе «Оптимизация HTML, CSS, JS на часто показываемых страницах Яндекса»). По возможности стоит также использовать AJAX-запросы вместо стандартных, а в них JSON вместо XML.

Применение gzip для сжатия HTML

Другим широко известным методом является gzip-/deflate-сжатие на стороне сервера (возможно, стоит кешировать результаты, чтобы не перегружать процессор ненужными вычислениями). В этом случае главное обеспечить поддержку «старых» браузеров (чтобы клиенты без поддержки gzipа смогли нормально зайти на ваш сайт). Как видно из таблицы, использование gzip-сжатия позволяет существенно сократить размер ответа от сервера.

Сжатие CSS-файлов

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

Аналогичным образом можно сжимать и файлы скриптов (JavaScript). Наиболее распространенными методиками упаковки JS-файлов являются Dean Edwards Packer и YUI Compressor. В некоторых случаях сжатие с помощью второй методики, а затем архивирование позволяет добиться лучших результатов, чем в первом случае. Однако, стоит проверять размер конечных файлов для каждого конкретного случая.

Для минимизации повторных запросов с клиентской машины на сервер используются различные приемы и методы кеширования. Наиболее простым, пожалуй, будет использование фоновых картинок (CSS-свойство background-image) вместо обычных (тег <img>). Это позволяет сообщить браузеру о том, что большинство элементов на странице используются в оформительских целях и для отображения их достаточно загрузить только один раз (зависит, естественно, от настроек браузера). Далее стоит упомянуть про набор заголовков Expires / Cache-Control, которые предназначены ля управления кешом клиента на стороне сервера. В этом случае стоит кешировать все редко изменяемые ресурсы (даже если они на ваш взгляд не так часто запрашиваются). Еще одним методом управления кеширования на стороне сервера является включение в ответ заголовка ETag, который сообщает клиенту уникальный идентификатор файла, что позволяет серверу отвечать кодом 304 (Not Modified, не изменился) на повторный запрос клиента и файл повторно не отдавать. Наконец, на фоне всех описанных техник вырисовывается проблема версионности: когда нужно принудительно отдавать клиенту более новую версию закешированного файла. И она стандартно решается путем добавления в конец имени файла в месте обращения к нему уникальной строки запроса (file?v.3.11.45).

Рассмотрим теперь слияние нескольких файлов. Если на странице требуется подключить несколько CSS-файлов, которые отвечают различным логическим частям сайта, это можно делать автоматически, добавляя в общий файл подключение всех необходимых через директиву @media. Для объединения большого количества JS-файлов, относящихся к различных частям веб-приложения, также стоит использовать автоматические решения для «сборки» единого файла. Для JS-файлов также хочется отметить возможность использования unobtrusive (ненавячивого) JavaScript для уменьшения размера файла, передаваемого на клиентскую машину. Наконец, для слияния большого числа фоновых картинок применяют технику CSS sprites, когда для вывода нескольких изображений используется один источник, а сами изображения «вырезаются» из него CSS-директивой background-position и собственными размерами.

Существует также некоторое количество методов «экстремального» слияния. Например, можно отдавать пользователю единый файл в качестве как CSS-, так и JS-, используя технику взаимных комментариев. Также можно просто определить все стили и скрипты в том же файле, который содержит и html-код (так сейчас делается на главной странице yandexа). Можно отдать пользователю все фоновые картинки в CSS-файле, используя их base64 представление (этот метод не работает в IE). Наконец, можно отметить и ряд других методик, сводящихся к уменьшению размера страниц, которые показываются пользователю по умолчанию (например, большинство поисковиков показывает только первые 10 результатов запроса).

В заключение обзора методик, уменьшающих количество и время запросов к серверу, хочется отметить создание распределенных систем для доставки контента до пользователя (Content Delivery Networks, CDN). Большинство таких систем использует либо циклические записи в DNS (когда одному домену соответствует несколько IP-адресов), либо распределение нагрузки специальным балансировщиком (аппаратным или программным). Он может распределять клиентские запросы по загруженности каждой машины серверного кластера, либо по их географической принадлежности.

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

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

В качестве заключения можно привести известный критерий хорошей доступности сайта, когда он полностью загружается за 10 секунд на модеме (общий размер всех файлов не должен быть больше 36 Кб). Также можно привести официальные данные по главной странице yahoo.com, которая была оптимизирована согласно вышеописанным правилам. В результате время загрузке уменьшилось с 2,4 секунд до 0,9 секунд (в 2,7 раза). Похожая статистика имеется и для некоторых других ресурсов (внутренних разработок высоконагруженных проектов).

Спасибо за внимание. Буду раз ответить на ваши вопросы.