- PVSM.RU - https://www.pvsm.ru -

Я хочу, чтобы сайты открывались мгновенно

Здравствуйте, меня зовут Александр Зеленин и я веб-разработчик. Я расскажу, как сделать так, чтобы ваш сайт открывался быстро. Очень быстро.

Я хочу, чтобы сайты открывались мгновенно - 1

Вступление

Тут не будет советов сделать «А» и получить супер выигрыш. Такого не бывает. В данной статье я хочу рассмотреть те факторы, которые можно ускорять и за счет чего. Других факторов нету. В следующий раз, когда будете делать свой сайт быстрее — будет понимание, за счет чего получаете выигрыш.

Статья ориентирована на продвинутых разработчиков!

Каждая из рассмотренных тем заслуживает отдельного поста, а то и не одного. Если будет интересно — обязательно расскажу подробнее.

Начнем с того, что действительно имеет значение для пользователя:

Я хочу, чтобы сайты открывались мгновенно - 2

  1. HTML начинает загружаться (TTFB)
  2. HTML, CSS и скрипты вверху страницы загружены. Страница отрисована (TTI)
  3. Полный функционал: дополнительный контент доступен, управляющие кнопки работают (TTLB)*

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

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

Что оптимизировать в первую очередь?

Необходимо провести замер описанных метрик сайта и выделить из них самые крупные — в таком порядке и оптимизировать. Сейчас это делать проще чем когда-либо — достаточно открыть, например, в chrome панель Network в инструментах веб-разработчика.

Прежде чем начать оптимизации необходимо знать

  1. Географическое расположение целевой аудитории (город(а), стран(ы). Иногда даже район может иметь значение.)
  2. Параметры каналов связи пользователей (Какие тарифы распространены в данном географическом расположении? А мобильный интернет?)
  3. Типы устройств доступа (PC, телефоны, планшеты)

Все рекомендации приведены для современной PC машины и соединения в 8 мегабит в секунду с пингом до столицы не превышающем 50мс. В вашем случае необходимо скорректировать цифры в зависимости от желаемых условий.

Из чего состоит открытие страницы

  1. Ожидание
    1. Ожидание очереди
    2. Прохождение прокси
    3. Resolve DNS
    4. Установление соединения (TCP handshakes)
    5. SSL handshake
    6. Отправка запроса
    7. Ожидание ответа (время до получения первого байта)

  2. Загрузка данных
  3. Выполнение скриптов
  4. Рендеринг
  5. Отрисовка

Ожидание

Ожидание — всё, что происходит до момента получения первого байта.

Ожидание очереди

Влияние: каждый запрос, при превышении ограничения
Факторы: количество одновременно запрашиваемых файлов с одного домена
Хорошее значение: 0 для значащего контента
Обратить внимание если для значащего контента значение больше нуля.

Браузеры имеет ограничение на одновременное количество соединений (одновременных загрузок) с одним и тем же доменом. В среднем от 4 до 8, но может варьироваться в зависимости от устройства, браузера и его версии. При этом данное ограничение применимо одновременно ко всем вкладкам.

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

Необходимые шаги:

  1. Переместить загрузку значащего контента в начало очереди
  2. Если значащий контент всё ещё создает очередь — объединить релевантные ресурсы*. CSS файлы в один, JS файлы в один, иконки объединить в спрайты либо поместить прямо в CSS (издержка, обычно, не превышает 10-20% и стоит того).
  3. Переместить загрузку опционального контента в конец очереди
  4. Объединить опциональные ресурсы по группам. Не стоит объединять всё-всё — лучше всего объединять скрипты, стили и графику по блокам, т.к. иногда они независимы и можно отображать их асинхронно.

* А ещё можно вот так сделать [1]

Ошибки оптимизации

Объединить всё в 1 файл (т.е. прям всё поместить в HTML файл, включая графику через inline image)
Некоторое ускорение это даст, за счет того что ожидания не будет, но:

  1. Отрисовка будет только тогда, когда всё загрузится. А достаточно было бы HTML+CSS.
  2. Теперь кэш бесполезен. Если сайт открывается секунду, то и второй раз будет секунда (вместо четверти, как описано далее).

В идеале и канал связи и ресурсы процессора надо нагружать одновременно, а не последовательно. Я так сделал на yac2013 [2], не успев корректно разделить эти процессы и потеряв более 100мс.

Хорошо, тогда помещаем CSS прям в HTML.

Да, это даст выигрыш на первую загрузку. И только.

  1. Кэш бесполезен, CSS грузится каждый раз заново.
  2. Если подключаются шрифты (вы же подключаете их inline, верно? а не отдельным файлом, иначе ж точно так же ждать), получаем +200-500кб к загрузке

Мм, окей. Давайте тогда объединим все-все js файлы в 1 и все-все css файлы в 1. Тогда же уместимся в лимиты любого браузера, ну и грузиться будет быстро.

На правильном пути, но это всё ещё некорректно. Вам не нужны все-все js и css файлы для начальной отрисовки страницы. Даже если часть не используется — они все равно занимают канал и процессорное время. Ну и ещё при любом изменении самого маленького файлика кэш инвалидируется и пользователю загружать всё заново.

Как сразу всё организовать хорошо?

Шрифты и иконки inline в CSS. Сделать CSS необходимый для отрисовки, а остальные загружать по 1 через менеджер зависимостей. Необходимый CSS грузится из шапки страницы, остальные через js. JS точно так же. Разместить статику на CDN.

Прохождение прокси

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

Распознавание DNS записи

Влияние: один раз за TTL
Факторы: количество используемых доменов, расположение серверов имен регистратора
Хорошее значение: 20-50мс
Обратить внимание если ваше значение превышает 80мс для целевой аудитории

Каждый использованный домен (включая первый документ) на странице требует полного цикла распознавания домена, который обычно занимает от 10 до 120мс. У многих DNS серверов имеются зеркала по всему миру.

Необходимые шаги:

  1. Определить территориальное расположение вашей целевой аудитории и выбрать регистратора с хорошим расположением серверов имен для вас.
  2. Если миграция серверов не планируется, можно увеличить TTL домена до больших значений — сутки, или вообще неделя.
  3. Если планируются запросы на поддомены или другие домены из скриптов или других отложенных источников необходимо указать
    <link rel="dns-prefetch" href="//example.com">

    в основном документе.

Установка соединения

Влияние: каждый запрос после простоя
Факторы: расстояние до сервера с данными
Хорошее значение: 50-80мс
Обратить внимание если значение 150мс и выше

Прежде чем начать отправку и получения данных браузер устанавливает соединение с севером. Оно состоит из трёх передач пакетов и проходит за 1.5 RTT (пинг до сервера х 1.5). Соединение устанавливается каждый раз, как надо загрузить данные, если нет доступных соединений. Как мы помним, браузер открывает не более определённого количества соединений. Если данные приходят в очередь на загрузку и есть активные соединения, то будут использоваться они, как только освободятся, т.е. дополнительной задержки нет. В случае если вся значимая информация сразу содержится в первом же html документе получаем задержку в 1.5 пинга, а если подгружается хотя бы ещё что-то, получаем задержку уже в 3 пинга. Если пинг составляет 100мс, то получаем итоговую потерю уже 300мс.

Шаги:

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

SSL

Влияние, возможности разгона, факторы: те же, что и при установке соединения
Хорошее значение: 100-150мс
Обратить внимание если значение 250мс и выше

Использование SSL увеличивает время установки соединения в несколько раз [3].

Количество передаваемых пакетов для установки соединения увеличивается 3 до 12 и 3 RTT. Это значит, что при задержке в 100мс если данные не содержатся в изначальном документе получим минимум 600мс задержку. Временем работы сервера можно пренебречь, т.к. в данном случае оно будет мало.

Шаги:

  1. Понять, что SSL действительно необходим и отказаться там, где он не нужен
  2. Действия для ускорения установки соединения так же ускорят загрузку SSL

Отправка запроса

Обычно занимает менее 1мс [4]. Честно говоря, из практики, не вспомню ни одной ситуации, когда было больше.

Довольно важный момент, который хотел бы отметить — абсолютно с каждым запросом отправляется ряд заголовков. Cookie так же являются заголовками. Если вы поместите в Cookie много данных — они будут каждый раз отправляться на сервер. Если действительно надо использовать много тяжелых cookie (уж не знаю почему, но допустим) — указывайте корректные пути у них, что бы отправлялись только туда, куда надо.

Ожидание ответа

Влияние: каждый запрос
Факторы: время работы сервера
Хорошее значение: 10-50мс
Обратить внимание если значение более 100мс

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

Шаги:

  1. Определить какие данные и каким образом можно кешировать
  2. Возвращать все данные из кэша

Загрузка данных

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

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

Важно: с увеличением пропускной способности канала пользователя уменьшается влияние данного фактора. Если основная целевая аудитория находится в столице и имеет 100мб/с (против 8мс/с взятых за эталон) то 1 мегабайт загрузится на порядок быстрее и из самого долго фактора он может стать одним из незначительных.

Ещё стоит упомянуть про «TCP slow start», но это тема для отдельного поста (где-то был на хабре, не нашел).

Шаги:

  1. Включить сжатие данных на сервере. Но, надо учитывать, что разархивирование данных тоже занимает время (зависит от конечного устройства), и, в некоторых ситуациях оправдана отправка несжатых данных.
  2. Убрать неиспользуемые данные из загрузки. Очень часто бывает, что подключается ряд библиотек, а, через какое-то время, часть становится неактуальны, но убрать их забывают.
  3. Разделить данные на необходимые и вторичные, и загружать именно в таком порядке. Иногда можно получить выигрыш на порядок.

Выполнение скриптов

Влияние: всегда
Факторы: объем скриптов, используемые алгоритмы
Хорошее значение: 0-50мс до отображения значащего контента, далее не значимо
Обратить внимание если после загрузки данных более 200мс уходят «в никуда»

После загрузки скриптов у браузера уходит какое-то время на то, чтобы распарсить их и выполнить.

Рендеринг

Влияние: всегда
Факторы: объем и качество стилей и вложенности блоков сайта
Хорошее значение: до 50мс
Обратить внимание если рендеринг занимает более 200мс

После загрузки всех стилей браузер начинает вычисления где какой блок расположить, где переносить строки и т.п.
Один раз видел 20% влияние на скорость работы сайта исключительно большой вложенности HTML. Серьёзно — оптимизация всего лишь вложенности HTML дала на 20% более отзывчивый сайт.

Шаги:

  1. Сократить количество стилей
  2. Избавится от переназначающих стилей по возможности (когда на одно свойство есть куча перекрывающих правил, а выполняется только одно. Смотреть в сторону OOCSS

Отрисовка

Влияние: всегда
Факторы: количество «тяжелых» элементов с постобработкой, таких как, например, тени. Количество графики.
Хорошее значение: до 50мс
Обратить внимание если отрисовка занимает более 200мс

Основное влияние на время отрисовки играют изменяющиеся части на странице. Банально одна gif’ка даст прирост больше чем что угодно другое.

Шаги:

  1. Уменьшить количество графики и динамичных элементов на странице

CDN

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

Браузерный кэш

Когда пользователь открывает сайт первый раз — кэш нам никак не поможет (за исключением использования публичных CDN с библиотеками, но это тема для отдельной статьи и вопрос контроля безопастности). Но при повторных заходах кэш предоставляет огромные преимущества. Весь неизменяемый контент должен помещаться в браузерный кэш. Графика, стили, скрипты. Также в кэш можно складывать ответы к API, но очень осторожно. При грамотном управлении кэшем у нас останется:

  1. 75мс на установку соединения (без дополнительных соединений, данные с кэша)
  2. 25мс ожидания ответа сервера
  3. 50мс на рендеринг
  4. 10мс на отрисовку
  5. 100мс загрузки данных (вместо 650)

Т.е. полная загрузка сайта в пределах четверти секунды!

Сокеты

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

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

У меня SPA, как быть?

Все советы применимы тем больше к SPA. Обычно SPA имеет минимум на 1 шаг больше до момента отрисовки контента, т.е. получили HTML -> получили скрипты -> загрузили контент из API. Отсюда получаем минимум ещё один RTT, причем после загрузки скриптов.

Действия:

  1. Возвращать HTML сразу с данными. Современные шаблонизаторы (типа handlebars) не привязаны к языку, и их легко генерить и на сервере, и на клиенте. Хотя бы для неавторизованных пользователей. У авторизованных уже есть кеш и издержка будет минимальна.
  2. Загружать в первую очреедь только необходимые контроллеры, модели, вьюшки и что там у вас ещё. Это проще чем кажется, если прописывать зависимости или использовать AMD

Ошибки оптимизации

А что если переместить запросы к данным сразу в HTML файл, что бы они в кэш сложились, и когда скрипты загрузятся всё уже было?

Сперва попробуем понять на чем можно выиграть. Выиграть при таком подходе мы можем только 1 RTT (за счет параллельности, хотя этим мы заблокируем один канал) и время ожидания сервера.

В случае если у вас сервер отвечает долго и вы по каким-то причинам не хотите оптимизировать его, что бы это ожидание составляло ~20мс то да, такой подход даст выигрышь (по сравнению с 3мя шагами). Но если вы сделали всё верно, то получаем RTT + 20мс. Т.е. максимум 50-70 миллисекунд. Это настолько несерьёзный выигрышь в сравнении с потенциальными проблемами от этого дейсвия что он того близко не стоит.

Возможные проблемы:

  1. Cкрипт загрузился раньше, чем получены данные и уже отправил новый запрос

Субъективные оптимизации

Бывает что можно сделать так, что бы казалось что работает быстрее, когда, на самом деле, так же. Хорошо этим пользоваться. В качестве примера могу привести переод прослушивания событий в SPA с click на mousedown (в дополнении к click!). Небольшой хак, позволяющий «попробовать»:

document.onmousedown = function(e) {
	e.target.click();
}

Трюк в том что у пользователя клик целиком занимает до 50мс, а событие onmousedown происходит на середине. Таким образом начав обработку раньше мы можем и выдать результат раньше. Если у нас обработка не превышает скорость клика юзера то у последнего будет ощущение что он ещё даже не кликнул, а уже всё показалось — вот это да! Подобные трюки надо побирать под каждый проект индивидуально.

Вообще, конечно, это частный случай — если что угодно просчитать заранее, то и показать можно сразу.

Можно ещё в кэш клать по mouseover более чем на 0.1с, например.

Итого

Пользователь: без прокси, с шириной канала 8 мегабит в секунду и RTT 50мс до нашего сервера имеет:

  1. 50мс на распознавание DNS
  2. 75мс на установку соединения (без SSL) (+75мс второе и последующие параллельные соединения)
  3. 25мс ожидания ответа сервера
  4. 0мс на выполнение скриптов
  5. 50мс на рендеринг
  6. 10мс на отрисовку

285мс издержек

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

Итого: 1 секунда

При повторном открытии уходит задержка на DNS и часть данных грузится из кэша.

Итого: ~0.4 секунды с кэшем

Делайте быстрые сайты и все будут довольны. Если при всем этом ещё показывать пользователю информацию при отключении связи [5], то вообще шикарно будет.

Бонусом можно почитать High Performance Browser Networking [6]

Задавайте вопросы — отвечу, дополню статью.

Всех с наступающим новым годом.

Автор: Zav

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/dns-2/107501

Ссылки в тексте:

[1] вот так сделать: http://habrahabr.ru/post/242255/

[2] yac2013: https://github.com/Zav39/yac2013/blob/gh-pages/index.html

[3] в несколько раз: http://www.semicomplete.com/blog/geekery/ssl-latency.html

[4] менее 1мс: https://developers.google.com/web/tools/chrome-devtools/profile/network-performance/resource-loading#resource-network-timing

[5] показывать пользователю информацию при отключении связи: http://habrahabr.ru/post/242161/

[6] High Performance Browser Networking: http://www.amazon.com/High-Performance-Browser-Networking-performance/dp/1449344763

[7] Источник: http://habrahabr.ru/post/274129/