Привет, меня зовут Стас Макеев. В Яндексе я руковожу разработкой технологии Турбо-страниц, которая обеспечивает быструю загрузку контента даже при медленном соединении. Сегодня я расскажу читателям Хабра немного об архитектуре нашего проекта.
На счастье пользователя во многом влияет то, насколько быстро он видит содержимое интернет-страницы. Скорость беспокоит многих: в магазине мобильных приложений только у Speedtest больше ста миллионов установок. Провайдеры, мобильные операторы, разработчики сайтов и приложений стремятся обеспечить максимально быстрый доступ к контенту, чтобы клиенты были довольны.
Средняя скорость загрузки в российских мобильных сетях составляет 16,26 Мбит/с — это довольно хороший показатель. Но скорость соединения неравномерна, мы всё ещё сталкиваемся с медленным интернетом — 3G, 2G, EDGE. Наверняка вы были в ситуации, когда в кафе или торговом центре, в дороге или на даче сильно снижается привычно высокая скорость: сайты загружаются десятки секунд, а то и дольше.
Технология Турбо-страниц решает проблему доступа к контенту, в том числе и при низкой или нестабильной скорости соединения. Это важно для владельцев сайтов, у которых снижается доля посетителей, «отваливающихся» на этапе перехода из поиска.
Как работают Турбо-страницы
Владелец сайта регистрирует 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. Для других компонент они во многом схожи.
В нашем CI-процессе есть задачи, принимающие пулл-реквесты в dev, которые выполняют базовые проверки на каждый коммит в feature-ветку. Если какие-то показатели превысят заданные границы, влитие в dev замораживается до выяснения причин.
Основные метрики на данном этапе:
- время шаблонизации;
- размер итоговой страницы;
- размер статических файлов.
Мы собираем клиентскую статику (CSS и JS) для каждой страницы в зависимости от данных, но сами бандлы с блоками не зависят от запроса, поэтому достаточно сравнить размер файлов в ветке с аналогичными файлами в dev. Для разных типов файлов у нас установлены различные пороговые значения, после которых задачу нельзя залить в dev без «ОК» от ответственных за скорость.
Как правило, происходит совместный разбор кода и поиски путей для оптимизации.
С метриками размера страницы и временем шаблонизации приходится действовать иначе, так как они сильно зависят от конкретного запроса и необходима какая-то статистическая достоверность. Более того, нельзя брать синтетические запросы, ведь это будут нечестные замеры. Поэтому мы постоянно собираем случайные запросы пользователей по access logs, формируем из них «патроны» и «стреляем» ими по шаблонам в ветке с изменениями и по dev. Это позволяет отлавливать изменения даже на не очень популярных запросах.
У нас есть несколько «корзинок запросов», которые позволяют покрыть большую часть трафика на Турбо-страницы.
Кроме оптимизации наших шаблонов, мы следим за оптимизациями, которые происходят внутри V8. Например, переход на TurboFan дал отличные результаты: значительно уменьшилось время серверной шаблонизации.
Время серверной шаблонизации уменьшилось после перехода на TurboFan
Сеть
В сетевую часть мы включаем всё, что происходит между клиентом и сервером: время передачи данных, размер страницы и статики, а также кешируемость ресурсов. Это уже интереснее, так как из наших уютных дата-центров мы попадаем в дикий внешний мир, где от нас уже зависит не всё. Измерения становятся немного сложнее, а главное — можно получить действительно ощутимые результаты в сотни миллисекунд.
Вот, что мы делаем.
У нас подкручены параметры TCP и TLS, которые позволяют выиграть несколько RTT (Round Trip Time), это даёт отличные результаты в сетях с высокой latency. Наши коллеги уже писали об этом, так что углубляться не буду.
Размер передаваемых данных может сильно влиять на скорость загрузки, поэтому мы стараемся присылать только то, что нужно текущей странице, в наиболее эффективном виде.
Картинки в наших интерфейсах оптимизируются с помощью ImageOptim. Для оптимизации SVG мы используем не только SVGO, но и не ленимся заглянуть в содержимое и при возможности оптимизировать его руками.
Изображения от владельцев сайтов мы выкладываем на специальный 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 довольно сомнительная;
- на ссылках бывает много обработчиков событий, между кликом и фактическим временем начала загрузки страницы может пройти ненулевое время, которое хотелось бы понимать.
Мы постоянно развиваем технологию, чтобы сайты привлекали больше посетителей. Сейчас Турбо-страница в среднем загружается в 15 раз быстрее, чем обычная мобильная версия. Десятки тысяч сайтов используют Турбо, и суммарное количество визитов на них — больше 12 миллиардов.
Всё это — результат труда разработчиков, службы поддержки, менеджеров, работающих с владельцами сайтов, и многих других. Со временем команда, конечно же, расширяется. Например, сейчас мы ищем специалистов по фронтенду и бэкенду и будем рады видеть новых коллег.
О каких компонентах технологии Турбо-страниц вы бы хотели прочитать в будущем более подробные технические материалы? Какой наш опыт вам был бы интересен? Мы также будем рады отзывам и идеям. Спасибо!
Автор: Команда Яндекса