Традиционная модель валидации ресурсов

Разработчики из Google вчера объявили о результатах совместного с Facebook и Mozilla проекта оптимизации браузера по образцу Firefox. После оптимизации значительно ускорилась повторная загрузка страниц в мобильной и десктопной версиях Chrome. По данным Google, в последней версии Chrome перезагрузка страниц ускоряется в среднем на 28%.

Обычно при перезагрузке страницы браузер осуществляет валидацию: отправляет сотни сетевых запросов только для проверки, что картинки и все остальные ресурсы в кэше всё ещё остаются действительными и их можно использовать повторно. Такой механизм перезагрузки страниц существовал без изменений много лет, несмотря на все изменения в сайтах и технологиях веб-разработки. Для маленьких элементов скорость выполнения этого запроса с получением ответа 304 HTTP примерно соответствует скорости обычного запроса с загрузкой элемента веб-страницы.

Пришло время улучшить браузеры и сайты.

Разработчик Google Такаси Тоёсима (Takashi Toyoshima) объясняет, что раньше валидация не приносила особенных хлопот, но с развитием веб-технологий сейчас на каждую страницу обычно приходятся сотни сетевых запросов, которые отправляются к десяткам разных доменов. Это особенно неприятно на мобильных устройствах, в которых пинг к серверам выше, чем на десктопе, а соединение нестабильно. Из-за больших задержек и нестабильного соединения возникают серьёзные проблемы с производительностью мобильных браузеров и скоростью загрузки страниц.

Первыми об улучшениях браузера подумали в Mozilla. Они ещё в прошлом году внедрили в Firefox 49 поддержку функции Cache-Control: immutable. С её помощью сервер может указывать ресурсы, которые никогда не изменяются. Такие элементы никогда не запрашиваются повторно. Например, шрифты, скрипты и таблицы стилей, а также десятки изображений, которые включены в оформление страницы. Так можно оптимизировать вообще любые ресурсы. Если ресурс изменяется, то его выкладывают с другим именем (в Facebook реализована система, где имена ресурсов соответствуют хэшам их содержимого).

В традиционной модели cache-control устанавливает ограниченное время жизни ресурса.

    $ curl https://example.com/foo.png
    > GET /foo.png
    
    < 200 OK
    < last-modified: Mon, 17 Oct 2016 00:00:00 GMT
    < cache-control: max-age=3600
    <image data>

По истечении этого срока браузер производит проверку действительности ресурса.

    $ curl https://example.com/foo.png -H 'if-modified-since: Mon, 17 Oct 2016 00:00:00 GMT'
    > GET /foo.png
    > if-modified-since: Mon, 17 Oct 2016 00:00:00 GMT
    
    If the image was not modified
    < 304 Not Modified
    < last-modified: Mon, 17 Oct 2016 00:00:00 GMT
    
    < cache-control: max-age=3600
    If the image was modified
    < 200 OK
    < last-modified: Tue, 18 Oct 2016 00:00:00 GMT
    < cache-control: max-age=3600
    <image data>

Параметр immutable снимает с браузера обязанность проверять действительность ресурса.

$ curl https://example.com/foo.png
> GET /foo.png
    
< 200 OK
< last-modified: Mon, 17 Oct 2016 00:00:00 GMT
< cache-control: max-age=3600, immutable
<image data>

Почти в то же время, как функция появилась в Firefox, Facebook начал модифицировать серверную инфраструктуру для поддержки этой функции. Разработчики Mozilla с удовлетворением отмечают, что и другие веб-сайты постепенно внедряют эту функцию, как и Facebook.

Разработчики Chrome тоже последовали примеру Firefox. Например, последняя версия Chrome делает на 60% меньше сетевых запросов для валидации кэшированных ресурсов, что означает ускорение скорости загрузки страниц в среднем на 28%.

В тестах Firefox результаты подтверждаются. Вместо средних 150 запросов на страницу Firefox отправляет всего 25, а в некоторых случаях загрузка страницы ускоряется в два раза.



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

Теперь Chrome и Firefox осуществляют перезагрузку страниц в упрощённом режиме — проверяются только основные ресурсы, а затем идёт обычная загрузка страницы. Такая оптимизация хороша во всех отношениях: скорость загрузки ускоряется, сетевой трафик снижается, экономится заряд батареи на мобильном устройстве. Facebook отрапортовал, что количество зпросов к статическим элементам на сервере уменьшается на 60%.

На видео показано, как происходит перезагрузка типичной страницы на Amazon со смартфона у американского сотового оператора. Видно, что время перезагрузки уменьшается с 20 секунд примерно до 13 секунд, причём в новой версии Chrome после прихода ответа с сервера страница обновляется на экране практически мгновенно, а не загружается по частям.


Коллеги из Facebook, которые наиболее заинтересованы в улучшении работы браузеров, помогали в разработке. Интересно, что в 2014 году инженеры Facebook провели исследование и выяснили, что именно Chrome присылает больше всего запросов, которые возвращают 304 HTTP.



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



Сейчас работа продолжилась, и с появлением «вечных» ресурсов обновления будут происходить ещё реже. Разработчики Chrome говорят, что данное изменение потребовало относительно небольших изменений в коде. Это наглядный пример, как маленькие оптимизации способны дать отличный результат.

Одновременно с поддержкой immutable-элементов Facebook одним из первых сайтов внедрил сжатие brotli. С его помощью сжимается динамическая разметка, которую нельзя кэшировать. По сравнению со старым gzip, применение brotli экономит примерно 20% трафика.

Поддержка immutable-элементов на сервере выгодна и для пользователей, и для владельца сервера, ведь снижаются трафик и нагрузка на CPU.
Поделиться с друзьями
-->

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


  1. 1andy
    27.01.2017 16:01
    +9

    Не понятно, чем immutable отличается от древнего способа установки что-т вроде
    Cache-control: max age=100000000
    Expires: 2050-10-10


    и удаления `E-tag/Last-Modified` — чтобы браузеры и не думали за 304 обращаться


    1. mayorovp
      27.01.2017 16:51
      +3

      В конце 2050 года все ваши сайты резко замедлятся...


      1. vlivyur
        29.01.2017 01:02

        Надолго ли?


        1. mayorovp
          29.01.2017 11:34
          -1

          Пока программист константу в коде не поменяет! :)


      1. amarao
        29.01.2017 22:48
        -2

        2050 года не будет. Будет что-то другое, много позже конца Эпохи.


        1. ankh1989
          29.01.2017 23:34

          Конец эпохи был вроде в 2012. Там какие то траблы с календарём майя были.


          1. amarao
            29.01.2017 23:36
            +2

            Код майя весь выпилили. От слова «весь». А вот юниксовый код — он всюду. Так что 2038 касается всех.


            1. mayorovp
              30.01.2017 09:39

              Есть мнение что к тому времени решение этой проблемы будет найдено. А константа 2050 в коде как была — так и останется :)


    1. Klimashkin
      28.01.2017 17:10
      +2

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


  1. tmin10
    27.01.2017 18:07

    Лучше бы попробовали решить проблему, что на медленном соединении (64 килобита на телефоне) часто страницы загружаюьтся полностью чистыми и тест отобюражается уже после полной загрузки страницы. Как я понимаю это вызвано медленной загрузкой шрифтов и прочих элементов. Также бывает (в мобильном firefox), что страница загружается, уже вижно контент, начинаю его читать, а ниже ничего нет! Это воовсе непонятно, html страничка же загружается в самом начале, а уже потом на ней начинают навешиватсья скрипты и стили. Наблюдаю такое на мобильной версии гиктаймса в комментариях.
    Вообще, раньше было лучше: сначала загружалась страничка, её уже можно было читать, а уже потом подгружались картинки и стили.


    1. ivan386
      27.01.2017 18:20

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

      Браузер отправляет запросы изображений стилей и скриптов до полной загрузки страницы(html). Я на бесплатном мобильном инете когда сидел наблюдал аналогичную картину. Сделал скрипт который скрывает изображения до полной загрузки страницы.


    1. Delics
      28.01.2017 11:12

      Гугл давно рекомендует всё подключать к странице с тегом async.
      Тогда страница и всякие скрипты/шрифты будут грузиться одновременно. Но т.к. html-страница обычно занимает меньше места, она загрузится первой.

      Технология есть, надо просто сайты модернизировать. Но за это никто не заплатит делать это некому.


      1. ivan386
        29.01.2017 01:51

        Тут проблема в том что всё грузится одновременно не давая html загрузится до конца. В итоге соединение обрывается и страница остаётся не загруженной.


  1. ivan386
    27.01.2017 18:13

    А мне нравится appcache. Обновление страницы и её контента проверяется одним запросом. И сайт может работать из кеша даже оффлайн. Жаль что ему установили статус нерекомендуемый но пока он всё ещё работает.


  1. dimkss
    27.01.2017 18:19
    +3

    Эм… А что будет если этот immutable ресурс загрузился не полностью, или скачаная версия содержит ошибку?
    Перезагрузка страницы не поможет, тогда переустанавливать браузер? (сарказм)


    1. tmin10
      27.01.2017 18:22

      Так кэш же почистить.


    1. ivan386
      27.01.2017 18:22

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


    1. faiwer
      27.01.2017 19:37

      Просто нажать "Hard reload"


      1. dimkss
        29.01.2017 12:24
        +3

        На мобилке будет трудно.


    1. mayorovp
      27.01.2017 23:31

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


      1. ivan386
        28.01.2017 19:58

        Если есть Content-Length браузер определяет что ресурс не полностью загружен. Но догружает его при следующем открытии страницы.


  1. pudovMaxim
    27.01.2017 22:17
    +1

    А что если в ответ по первому запросу к странице выдавать полный список версий ресурсов с этой страницы или просто устаревших ресурсов. Браузер смотрит что протухло, и грузит только устаревшие версии. Что-то в духе:


    Cache-outdated: main.css, image.png, script.js


    Или такое уже есть в симпсонах стандарте?


    1. pash7ka
      28.01.2017 17:10
      +1

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


      1. pudovMaxim
        28.01.2017 20:51

        Я вот так себе это фантазирую(на примере nginx+php):
        первый запрос: php генерирует страницу, выдает ее nginx-у, тот отдает клиенту, затем смотрит в содержимое, ищет все ресурсы, приписывает какой-то тег странице со списком ресурсов и их версий(или временем изменения), затем сохраняет страницу в свой кэш.


        последующие запросы: nginx берет страницу из кэша, отдает клиенту. Клиент смотрит этот тэг, видит, что страница имеет следующие ресурсы A.ver123, B.ver35, Z.ver5. Смотри в свой кэш, находит эти ресурсы, если совпадают, то сразу же по ним возвращает 304 и рендерит страницу. Если же есть расхождения, то делает запросы только по ним.


        Возможно это хорошо только для кэшируемых страниц.


        1. pash7ka
          28.01.2017 21:06

          В вашем сценарии придётся повесить на nginx задачу по парсингу содержимого (как? хорошо если оно в html или css прописано, а если там JS генерирует имена картинок по каким-то своим правилам?), заниматься кешированием (где и как долго хранить кеш?) или припиской вычисленного тэга к уже имеющемуся кешу.
          А зачем?
          Проблема в предложенном подходе с immutable — только одна: невозможность смены содержимого ресурса без смены его имени. Но это проблема организационная. И решение её давно придумано — называть ресурсы по хэшу их содержимого.
          А ваша идея создаёт технические проблемы которые кому-то придется постоянно решать.


          1. pudovMaxim
            28.01.2017 21:31

            Спасибо. Сказав про имена, до меня дошел принцип работы immutable :)


        1. ivan386
          28.01.2017 21:38
          +1

          Appcache же.


          1. За место имён ресурсов используем хеш
          2. Отсылаем хедер с ними
            Cache-Control: immutable

          index.html


          <!DOCTYPE HTML>
          <html manifest="cache.appcache">
            <body>
              …
            </body>
          </html>

          cache.appcache:


          CACHE MANIFEST 
          /cf298295d05da05257c0cb7ce8af7dcc8ef95e35.css
          /2d408aaa5a340d732402a346a7f915ed8a3d8a04.js
          /cbb33e652dd64ca308905a138dec165c4619ae32.png

          В результате браузер должен запросить только новые файлы.


          1. pudovMaxim
            28.01.2017 22:26

            Да, точно, оно самое. Спасибо.


          1. faiwer
            29.01.2017 06:27

            Надеюсь вы шутите :) Во-первых он deprecated, ибо теперь service workers. Во-вторых в рамках обсуждаемого вопроса, это как из пушки по воробьям. Там у этого AppCache (который вообще для offline приложений) столько нюансов…


            1. ivan386
              29.01.2017 09:36
              +1

              Не смотря на deprecated AppCache всё ещё работает. Service Worker пока ещё
              на эксперементальной стадии.


        1. mayorovp
          29.01.2017 11:44

          Этот сценарий успешно реализуется в рамках текущего HTTP и именно он еще больше улучшается с введением immutable. Никакого Cache-outdated не нужно.


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


  1. Mingun
    28.01.2017 15:53
    -2

    Странно, вроде бы фичу первым внедрил Firefox, а преподносится всё так, словно за неё надо благодарить Chrome… Ох уж эти маркетологижурналистыАлизар…


  1. amarao
    29.01.2017 22:51
    +1

    У меня есть давняя мысль, что URL в современном мире работает не очень хорошо.

    Представьте себе на секунду, что мы бы говорили не про 'http', а про 'world wide git'. Страница — набор коммитов. Браузер может делать либо нормальный pull (без истории), либо скачивать только те коммиты, которые устарели.

    Файлы идентифицируются по хешу, т.е. устаревание файла определяется по смене хеша, на который ссылаются.

    Говорю это по наблюдению за браузером, когда pull для проекта проходит быстрее, чем открытие его страницы на гитхабе.


    1. ivan386
      30.01.2017 02:23
      +1

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