HTTP/2 для самых медленных

Уже более трёх лет прошло с конца 2015 года с тех пор как поддержка HTTP/2 появилась в большинстве популярных браузеров, но согласно w3techs.com лишь 37% сайтов используют его на текущий момент.

Чего же мы все ждём? Ведь это даёт почти бесплатный прирост производительности, уменьшается overhead на открытие дополнительных соединений, увеличивается скорость загрузки сайта для конечного пользователя. Всё что нужно лишь добавить несколько строк в конфиг Nginx. Заодно и оценим насколько это может увеличить комфорт пользователей.

Для измерения скорости загрузки сайта есть замечательный инструмент WebPageTest. Помимо скорости загрузки он также показывает основные проблемы с производительностью.

Проверим исходное состояние нашего сайта (для тестирования выбрано подключение Cable 5 Mbit/sec)

Время загрузки > 7 секунд, рендеринг начинается на 3.5 секунде.

Вверху страницы выводится панель показывающая оценку различных аспектов производительности

Начнём с самого жирного, WebPageTest подсказывает нам что у нас не используется сжатие для статики

Очень странно ведь в конфиге Nginx включена опция gzip_static on;. Ну ладно SVG в каком-то смысле экзотика, но не жмётся JS? Оказывается что типы для которых разрешено сжатие нужно задавать явно с помощью gzip_types.

gzip_types text/plain text/css application/javascript 
  application/x-javascript text/xml application/xml 
  application/xml+rss text/javascript application/json 
  image/svg+xml application/font-woff application/font-woff2 
  font/woff font/woff2;

Неплохо, уже секунду срезали. Дальше попробуем включить HTTP/2, в теории достаточно добавить http2 в тег listen 443 http2 ssl; оставим за кадром что вам придётся пройти чтобы сделать это (обновить систему, обновить Nginx, переписать половину скриптов автоматизации), лучше посмотрим что получилось:

Получили уменьшение времени загрузки около 30%, очень хорошо. На waterfall view видно как работает HTTP/2 сразу после загрузки HTML-страницы загрузка ассетов app.js и app.css начинается параллельно и без открытия дополнительных соединений, то же самое происходит и для остальных ассетов далее.

Дальше сложнее, самое простое уже сделали, но WebPageTest говорит, что есть возможность пожать картинки. Отлично, с JPEG всё понятно, подкрутили компрессию, но есть несколько картинок про которые WebPageTest ничего не говорит, но в waterfall view они слишком выделяются своими широкими полосками, почти 250 КБ, ничего себе!

Картинка PNG, поэтому WebPageTest ничего и не предлагает, lossless сжимать больше некуда. Начинаем думать: в JPG её перегнать нельзя, тут важно сохранить прозрачность, что-то я там слышал про JPEG2000…

Оказывается есть уже куча современных форматов сжатия, но проблема в том что не все они поддерживаются всеми браузерами Apple топит за JPEG2000, Google за WebP. Оба они дают значительное преимущество над PNG так как используют сжатие с потерями, но при этом поддерживают прозрачность. Плюс есть группа маргинальных браузеров которые не поддерживают ни то ни другое, что же делать? На помощь приходит HTML5 тег picture.

<picture>
  <source type="image/webp" srcSet="/assets/images/top-slide-3.webp"/>
  <source type="image/jp2" srcSet="/assets/images/top-slide-3.jp2"/>
  <source type="image/png" srcSet="/assets/images/top-slide-3.png"/>
  <img src="/assets/images/top-slide-3.png" />
</picture>

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

Также для ускорения отображения хедера можно использовать новую технологию server-push на базе HTTP/2, она позволяет серверу самостоятельно инициировать отправку нужного ассета клиенту, не дожидаясь от него запроса, т.е. к моменту когда браузер скачает, HTML, JS, обработает всё это и поймёт что ему нужна ещё пара картинок, они уже будет получены. Будем пушить картинки из хедера. Для этого используется HTTP header Link. Например так:

Link: </assets/images/top-slide-1-57cc822daa.webp>; rel=preload; as=image, 
  </assets/images/top-slide-2-fbdfce811c.webp>; rel=preload; as=image

Что бы сервер инициировал пуш видя такой заголовок в ответе, нужно включить эту фичу, для Nginx это http2_push_preload on;. В итоге получим:

Видим на waterfall view что загрузка картинок их хедера начинается сразу после получения HTML клиентом.

Итого:

  • общий размер передаваемых данных уменьшился с 3.7 МБ до 1.7 МБ
  • время загрузки страницы уменьшилось с 7.2 сек. до 2.8 сек.
  • начало рендеринга страницы теперь происходит на 1.9 сек вместо 3.5