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

«Под капотом» Турбо-страниц: архитектура технологии быстрой загрузки веб-страниц

Привет, меня зовут Стас Макеев. В Яндексе я руковожу разработкой технологии Турбо-страниц, которая обеспечивает быструю загрузку контента даже при медленном соединении. Сегодня я расскажу читателям Хабра немного об архитектуре нашего проекта.

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

Средняя скорость загрузки в российских мобильных сетях составляет 16,26 Мбит/с [1] — это довольно хороший показатель. Но скорость соединения неравномерна, мы всё ещё сталкиваемся с медленным интернетом — 3G, 2G, EDGE. Наверняка вы были в ситуации, когда в кафе или торговом центре, в дороге или на даче сильно снижается привычно высокая скорость: сайты загружаются десятки секунд, а то и дольше.

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

«Под капотом» Турбо-страниц: архитектура технологии быстрой загрузки веб-страниц - 1

Как работают Турбо-страницы

Владелец сайта регистрирует RSS-фид в Яндекс.Вебмастере. Фид попадает в контент-систему Турбо-страниц, которая раз в несколько минут забирает из него обновления. Тяжёлый контент — в первую очередь картинки и видео — мы кешируем и раскладываем в CDN. Кроме RSS, контент можно передавать через API и автопарсер.

Объём закешированных изображений Турбо-страниц приближается к 100 Тб

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

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

«Под капотом» Турбо-страниц: архитектура технологии быстрой загрузки веб-страниц - 2

Что происходит, когда вы открываете URL в браузере?

Когда пользователь переходит на Турбо-страницу, «под капотом» происходит примерно следующее:

«Под капотом» Турбо-страниц: архитектура технологии быстрой загрузки веб-страниц - 3

HTTP adapter обрабатывает HTTP-запрос пользователя и делает запрос в нужный граф в AppHost и report-renderer.

AppHost — специальная компонента, которая инкапсулирует сетевое взаимодействие источников, описанное в виде графа зависимостей. Источники опрашиваются в порядке топологической сортировки на этом графе, вся бизнес-логика зашита в них самих и в конфигурации графа. В частности, на уровне графа опрашивается KV-storage и отправляется запрос данных в сторонние API.

Report-renderer — приложение, написанное на node.js, которое принимает на вход JSON, исполняет шаблоны, написанные на JS, и возвращает строчку.

Всё это происходит почти мгновенно.

Что влияет на скорость загрузки?

Мы работаем над всеми аспектами скорости: от внедрения HTTP/2 на балансере и оптимизации TLS-хендшейка до ручной оптимизации SVG. При этом нужно понимать, из чего складывается конечная пользовательская скорость.

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

Сервер

Сюда входит всё, что происходит в дата-центрах: от момента, когда HTTP-запрос поступает к нам на сервер, до генерации HTML-страницы, которая отдаётся непосредственно клиенту.

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

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

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

В нашем CI-процессе есть задачи, принимающие пулл-реквесты в dev, которые выполняют базовые проверки на каждый коммит в feature-ветку. Если какие-то показатели превысят заданные границы, влитие в dev замораживается до выяснения причин.

Основные метрики на данном этапе:

  • время шаблонизации;
  • размер итоговой страницы;
  • размер статических файлов.

Мы собираем клиентскую статику (CSS и JS) для каждой страницы в зависимости от данных, но сами бандлы с блоками не зависят от запроса, поэтому достаточно сравнить размер файлов в ветке с аналогичными файлами в dev. Для разных типов файлов у нас установлены различные пороговые значения, после которых задачу нельзя залить в dev без «ОК» от ответственных за скорость.

Как правило, происходит совместный разбор кода и поиски путей для оптимизации.

С метриками размера страницы и временем шаблонизации приходится действовать иначе, так как они сильно зависят от конкретного запроса и необходима какая-то статистическая достоверность. Более того, нельзя брать синтетические запросы, ведь это будут нечестные замеры. Поэтому мы постоянно собираем случайные запросы пользователей по access logs, формируем из них «патроны» и «стреляем» ими по шаблонам в ветке с изменениями и по dev. Это позволяет отлавливать изменения даже на не очень популярных запросах.

У нас есть несколько «корзинок запросов», которые позволяют покрыть большую часть трафика на Турбо-страницы.

Кроме оптимизации наших шаблонов, мы следим за оптимизациями, которые происходят внутри V8. Например, переход на TurboFan [2] дал отличные результаты: значительно уменьшилось время серверной шаблонизации.

«Под капотом» Турбо-страниц: архитектура технологии быстрой загрузки веб-страниц - 4
Время серверной шаблонизации уменьшилось после перехода на TurboFan

Сеть

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

Вот, что мы делаем.

У нас подкручены параметры TCP и TLS, которые позволяют выиграть несколько RTT (Round Trip Time), это даёт отличные результаты в сетях с высокой latency. Наши коллеги уже писали [3] об этом, так что углубляться не буду.

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

Картинки в наших интерфейсах оптимизируются с помощью ImageOptim. Для оптимизации SVG мы используем не только SVGO [4], но и не ленимся заглянуть в содержимое и при возможности оптимизировать его руками.

Изображения от владельцев сайтов мы выкладываем на специальный CDN, оптимизированный для отдачи изображений. Мы обрезаем exif и цветовой профиль изображения, предварительно сконвертировав изображение в sRGB. Битность приводится к 8 битам на канал, уровень сжатия выставляется в 85. Для ресайза используется фильтр lanczos.

Мы создаём десятки вариантов каждой картинки под комбинации различных размеров экранов с учётом плотности пикселей (retina-дисплеи). И конечно же, автоматически кодируем изображения в формат WebP, если его поддерживает браузер.
Текстовые форматы (HTML, JavaScript, CSS) сжимаются с помощью gzip/zopfli и brotli, если браузер его поддерживает.

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

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

Клиент

Мало сформировать ответ сервера и доставить его в браузер по сети, его ещё нужно эффективно показать. Мы оптимизируем время начала отрисовки страницы, чтобы человек быстрее начал читать содержимое.

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

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

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

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

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

Так мы сформулировали интегральную метрику скорости:
max (firstContentfulPaint, firstImageLoadTime, timeToVisible) — timeToClick

Где:

  • timeToClick — абсолютное время клика, который привёл к показу Турбо-страницы. Это может быть клик по сниппету на странице результатов поиска или по карточке в Яндекс.Дзене.
  • firstImageLoadTime — абсолютное время загрузки первого контентного изображения в первом экране.
  • timeToVisible — абсолютное время перехода страницы в состояние visible. Это актуально для случаев, когда страница была загружена в фоне.

И получили метрику пользовательского опыта:

  • если 2/3 экрана занимает изображение, которое ещё не загрузилось, честность метрики firstContentfulPaint довольно сомнительная;
  • на ссылках бывает много обработчиков событий, между кликом и фактическим временем начала загрузки страницы может пройти ненулевое время, которое хотелось бы понимать.

«Под капотом» Турбо-страниц: архитектура технологии быстрой загрузки веб-страниц - 5

Мы постоянно развиваем технологию, чтобы сайты привлекали больше посетителей. Сейчас Турбо-страница в среднем загружается в 15 раз быстрее, чем обычная мобильная версия. Десятки тысяч сайтов используют Турбо, и суммарное количество визитов на них — больше 12 миллиардов.

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

О каких компонентах технологии Турбо-страниц вы бы хотели прочитать в будущем более подробные технические материалы? Какой наш опыт вам был бы интересен? Мы также будем рады отзывам и идеям. Спасибо!

Автор: Команда Яндекса

Источник [6]


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

Путь до страницы источника: https://www.pvsm.ru/yandeks/324177

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

[1] 16,26 Мбит/с: https://www.speedtest.net/reports/ru/russia/

[2] TurboFan: https://v8.dev/docs/turbofan

[3] писали: https://habr.com/ru/company/yandex/blog/358944/

[4] SVGO: https://github.com/svg/svgo

[5] специалистов по фронтенду и бэкенду: https://yandex.ru/turbo?text=job-backend-frontend&utm_source=habr&utm_content=post170719

[6] Источник: https://habr.com/ru/post/460373/?utm_source=habrahabr&utm_medium=rss&utm_campaign=460373