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

Вы когда-нибудь загружали сайт на телефоне — и смотрели в течение 10 секунд на страницу без текста? Никто не любит сидеть и глядеть на пустой экран, пока загружается какой-то необычный шрифт. Поэтому есть смысл перенести загрузку таких важных вещей на максимально раннее время. Предзагрузка Link rel=preload должна была частично решить проблему. Первым делом браузер парсит HTTP-заголовки, так что это идеальное место, чтобы указать на предварительную загрузку ресурса, который точно понадобится позже.

По умолчанию интернет работает медленно


Давайте посмотрим, что произойдёт, если не использовать предзагрузку. Браузер может начать загрузку ресурсов только после того, как обнаружил, что он в них нуждается. Это быстрее всего происходит для ресурсов, которые находятся в HTML во время первоначального разбора документа (например, <script>, <link rel=stylesheet> и <img>).

Медленнее скачиваются ресурсы, которые обнаруживаются после построения дерева рендеринга (это место, где страница тормозит из-за загрузки шрифта, ведь чтобы понять это, сначала нужно загрузить таблицу стилей, разобрать её, построить объектную модель CSS, а затем дерево рендеринга).

Ещё медленнее загружаются ресурсы, которые добавлены в документ с помощью загрузчиков JavaScript, которые запускаются событиями вроде DOMContentLoaded. Если собрать всё вместе, мы получаем неоптимизированный и довольно бессмысленный водопад. Значительную часть времени канал простаивает, а ресурсы загружаются или раньше, чем необходимо, или слишком поздно:



Link rel=preload очень помогает


В последние несколько лет ситуация улучшилась благодаря Link rel=preload. Например:

Link: </resources/fonts/myfont.woff>; rel=preload; as=font; crossorigin
Link: </resources/css/something.css>; rel=preload; as=style
Link: </resources/js/main.js>; rel=preload; as=script

Благодаря этим директивам браузер может начать загрузку ресурсов сразу после получения заголовков и перед началом анализа тела HTML:



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

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

Задействуем время «размышления сервера» с помощью «ранних подсказок»


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

Иногда время размышлений сервера меньше, чем задержка сети. Иногда существенно больше. Но важно понимать, что они не пересекаются. Пока сервер думает, мы не отправляем данные клиенту.

Но интересно, что ещё до генерации ответа вы уже знаете некоторые стили и шрифты, которые необходимо загрузить для отображения страницы. В конце концов, страницы ошибок обычно используют тот же фирменный стиль и дизайн, что и обычные страницы. Было бы здорово отправить эти заголовки Link: rel=preload ещё перед работой сервера. Именно для этого задуман стандарт «ранних подсказок» Early Hints, указанный в RFC8297 от Рабочей группы HTTP за авторством моего коллеги Кадзухо Оку из Fastly. Оцените магию нескольких строк состояния в одном ответе:

HTTP/1.1 103 Early Hints
Link: <some-font-face.woff2>; rel="preload"; as="font"; crossorigin
Link: <main-styles.css>; rel="preload"; as="style"

HTTP/1.1 404 Not Found
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: <some-font-face.woff2>; rel="preload"; as="font"; crossorigin
Link: <main-styles.css>; rel="preload"; as="style"

Сервер может записать первый, так называемый «информационный» код ответа, как только получит запрос, и выдать его в сеть. Затем займётся определением реальной реакции и её генерацией. Между тем в браузере можно намного раньше начать предварительную загрузку:



Конечно, это потребует определённых изменений в работе браузеров, серверов и CDN, и разработчики некоторых браузеров высказали оговорки относительно трудностей реализации. Поэтому до сих пор неясно, когда данные заголовки можно внедрить в эксплуатацию. Вы можете отслеживать прогресс в публичных трекерах для Chrome и Firefox.

Мы рассчитываем, что в конечном итоге вы сможете выдавать заголовки Early Hints прямо из Fastly, по-прежнему отправляя запросы стандартным образом. Мы ещё не решили, как выставить интерфейс через VCL, поэтому сообщите, если у вас есть пожелания на этот счёт!

Но что насчёт HTTP/2 Server Push?


С HTTP/2 идёт новая технология под названием Server Push, которая вроде бы решает ту же проблему, что и Link rel=preload в ответе Early Hints. Хотя она действительно работает (и вы даже можете быстро генерировать кастомные пуши с edge-серверов в Fastly), но есть существенная разница по нескольким пунктам:

  • Сервер не знает о наличии ресурса у клиента, поэтому часто пушит его без необходимости. Из-за буферизации сети и задержки клиент обычно не может отменить отправку перед получением всего содержимого. (Хотя есть возможное решение этой проблемы, в виде предлагаемого заголовка Cache Digest, над которым Кадзухо работает вместе с Йоавом Вайсом из Akamai).
  • Запушенные ресурсы привязаны к соединению, поэтому легко запушить ресурс, который клиент не использует, так как он пытается получить его через другое соединение. Клиентам может потребоваться использовать другое подключение, поскольку ресурс находится в другом источнике с другим сертификатом TLS или поскольку он извлекается в другом режиме учётных данных.
  • H2 Push не очень последовательно реализован в разных браузерах. Поэтому трудно предсказать, будет он работать или нет в вашем конкретном случае.

Так или иначе, Early Hints и Server Push предлагают разные компромиссы. Подсказки Early Hints обеспечивают более эффективное использование сети в обмен на дополнительный обмен пакетами. Если вы предполагаете малую задержку в сети и длительное время на обдумывание сервера, то Early Hints — правильное решение.

Однако, это не всегда так. Давайте будем оптимистами и представим, что люди скоро поселятся на Марсе. Они будут просматривать веб-страницы с задержкой 20-45 минут на каждый обмен пакетами, поэтому дополнительный обмен исключительно болезненен, а серверное время несущественно по сравнению с ним. Здесь легко побеждает Server Push. Но если мы когда-либо будем просматривать веб-страницы с Марса, то скорее скачаем какой-то пакет с данными, нечто вроде предлагаемых сейчас веб-пакетов и подписанных обменов.

Дополнительный бонус: ускоренное свёртывание запроса


Хотя Early Hints предполагается использовать в первую очередь в браузере, но есть интересная потенциальная выгода и для CDN. Когда Fastly получает много запросов на один и тот же ресурс, мы обычно отправляем источнику только один из них, а остальные помещаем в очередь ожидания. Этот процесс известен как свёртывание запроса (request collapsing). Если ответ от источника включает в себя Cache-Control: private, то следует убрать из очереди все запросы и отправить источнику их по отдельности, потому что мы не можем использовать один ответ для удовлетворения нескольких запросов.

Мы не можем принять решение, пока не получен ответ на первый запрос, но в случае поддержки Early Hints, если сервер выдал ответ Early Hints с заголовком Cache-Control, то мы намного раньше узнаем, что очередь нельзя свернуть в один запрос, а вместо этого немедленно направим источнику все запросы из очереди.

Заказ менее критического контента с подсказками о приоритете


Ранние подсказки — отличный способ получить доступ к некоторым из самых ценных объектов в очереди (водопаде): когда сеть простаивает, то пользователь ждёт, в пути только один запрос, и на экране ничего нет. Но как только загружен HTML и страница проанализирована, то резко возрастает количество ресурсов, которые нуждаются в загрузке. Теперь важно не загружать ресурсы как можно быстрее, а загружать их в правильном порядке. Браузеры используют поразительно сложный массив эвристик, чтобы самостоятельно определить приоритет загрузки, но если вы хотите переопределить их, то в будущем это можно будет сделать с помощью подсказок приоритета (Priority Hints):

<script src="main.js" async importance="high"></script>
<script src="non-critical-1.js" async importance="low"></script>

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

Это можно использовать?


Ни ранние подсказки, ни подсказки приоритета пока не стали стандартом. Недавно H2O, сервер HTTP/2, используемый и поддерживаемый Fastly, начал применять Early Hints (см. PR 1727 и 1767), и есть намерение реализовать Priority Hints в Chrome, также как активное отслеживание ответов 1xx. В то же время нет никакого вреда в том, чтобы уже сейчас начать отправлять Early Hints — и если вы хотите опередить тренд, то вперёд!

Комментарии (11)


  1. firedragon
    23.08.2018 18:17
    +2

    Чем то мне это напоминает !important.
    Точнее постановку костылей.


    1. demimurych
      23.08.2018 23:30

      Вы наверное не так поняли смысл того что пытаются внедрить. Я опробую описать Вам понятнее
      1. Идет запрос к серверу — покажи мне страницу page1.
      2. Сервер что-то у себя там ищет, может быть обрабатывает какие то скрипты, формирует страницу. Все это занимает скажем 100мс. Через 100 мс шлет ответ — текст страницы.

      То есть эти 100мс, бруазер стоит и ничего не делает. Хотя уже целых 100мс мог бы грузить какую то статику.

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

      Картинки, css, шрифты и так далее.

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

      И это нисколько не костыль, сколько оптимизация под реальные условия.


      1. immaculate
        24.08.2018 03:30

        Суть в том, что этот механизм будет работать только при условии грамотного и вдумчивого использования. А так уже никто не делает. 95% сайтов делаются по принципу «тяп-ляп, react, и в production». Соответственно, все эти preload-ссылки будут генерироваться автоматически, и подгружать тонны мусора.


        Это костыль, потому что подразумевает вдумчивую и ручную оптимизацию, которую никто делать не станет.


        Так же, как !important — предназначался для очень специфичных случаев. Но в итоге, многие разработчики каждый раз добавляя что-то в стили, приписывают в конце !important, чтобы «наверняка работало». После чего поддерживать стили становится очень сложной задачей.


    1. immaculate
      24.08.2018 03:26

      Теперь гении Javascript напишут какой-нибудь UnicornBundler, который будет автоматом генерировать бандл из preload ссылок на все используемые стили и скрипты из package.json. Пускай браузер грузит на каждый чих 2,500 дополнительных ссылок. Все станет еще быстрее!


  1. Sabubu
    23.08.2018 18:50
    +2

    > Никто не любит сидеть и глядеть на пустой экран, пока загружается какой-то необычный шрифт.

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

    Пример: раньше мануал PHP использовал стандартные шрифты. Потом перешел на кастомные — и теперь при открытии страницы из Гугла секунду-две приходится смотреть на страницу без текста. Ну дебилы же, чесслово. Я теперь больше времени трачу из-за них.

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

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

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

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

    Стремление городить костыли, конечно, поражает.


    1. mayorovp
      24.08.2018 10:38

      И с каких же это пор в css нельзя указывать фолбеки на стандартный шрифт? Вот совсем недавно видел:


      .ff-slab {
          font-family: 'Roboto Slab', serif;
      }


      1. Sabubu
        25.08.2018 12:30

        Это фоллбек вида «если первый шрифт недоступен, взять второй».

        Я имел в виду фоллбек вида «если в этом шрифте нет иероглифа, взять его из другого шрифта, а не рисовать квадратик». Эта проблема по моему актуальна, так как большинство шрифтов содержат только ограниченный набор символов (иначе бы они весили десятки мегабайт), а отображать в идеале надо все символы.


        1. mayorovp
          25.08.2018 12:52

          Именно так фолбек и работает. Прямо сейчас проверить можно здесь: ru.stackoverflow.com/conduct

          На сайте для заголовков используется шрифт Roboto Slab, но в версии шрифта с сайта нет кириллицы, поэтому заголовки отображаются стандартным системным шрифтом.


  1. dagen
    23.08.2018 21:56

    Одна проблема с rel=preload: этот мета-тег не совместим с script type=module и nomodule. Чтобы разруливать эту ситуацию, надо читать user-agent сервером пререндера и соответствующим образом формировать html. Но тогда более логичным выглядит http2 push (ну или Early Hints). А если нет сервера пререндера, то приходится выбирать: либо доставка 90% аудитории ES2017 кода, либо preload. И первый выбор кажется намного вкуснее.


  1. aab137
    24.08.2018 09:59

    Большое спасибо за статью!
    Буду использовать


  1. hzs
    24.08.2018 14:54

    Если страница грузится 10 секунд, то тут уже ничего не поможет, кроме как отвесить пиздюлей криворуким разработчикам.
    Любая страница должна грузиться максимально быстро, а тяжёлый контент должен грузиться либо только по запросу пользователя, либо если основной контент уже загружен, и доступен пользователю, чтобы пользователю не пришлось ждать.