Хабрахабр

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

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

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

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

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

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

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

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

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

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

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

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

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. Больше внимания хочется уделить второй составляющей — времени исполнения каждой из вершин. Для других компонент они во многом схожи.

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

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

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

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

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

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

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

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


Время серверной шаблонизации уменьшилось после перехода на TurboFan

Сеть

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

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

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

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

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

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

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

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

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

Клиент

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

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

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

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

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

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

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

Где:

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

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть