Хабрахабр

Системный подход к скорости: онлайн-измерения на фронтенде

Команда скорости Яндекса вручную оптимизирует поисковую выдачу. Делать это вслепую трудно и зачастую просто бесполезно. Поэтому в компании построили инфраструктуру для сбора метрик, тестирования скорости и анализа полученных данных.

О том, какие метрики стоит использовать и как все оптимизировать, знает разработчик интерфейсов Яндекса Андрей Прокопюк (Andre_487).

Под катом — и видеозапись, и текстовая версия доклада.
В основе материала — выступление Андрея на конференции HolyJS.

В пару к этому докладу об онлайн-измерениях есть доклад Алексея Калмакова (тоже из Яндекса) об офлайн-измерениях, в его случае нет текстовой версии, но доступна видеозапись.

Над ними в компании работают более 50 человек, и чтобы скорость выдачи не падала, мы постоянно присматриваем за разработкой. Поисковая выдача Яндекса состоит из множества различных блоков, классов ответов на пользовательские запросы.

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

Чтобы ответить на этот вопрос, расскажу две истории.

История внедрения специфичного веб-шрифта на выдаче

Устроив эксперимент с шрифтами, мы обнаружили, что среднее время отрисовки контента ухудшилось на 3%, на 62 миллисекунды. Не так уж много, если принять это за дельту в вакууме. Заметная невооруженным глазом задержка начинается только со 100 миллисекунд — и все же время до первого клика сразу увеличилось на полтора процента.

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

Ведь эти цифры кажутся маленькими, пока не вспомнишь о масштабе сервиса. Фичу со шрифтами мы выкатывать не стали. В реальности полтора процента — сотни тысяч человек.

За одним обновлением с долей некликнутых — 0,4% последуют еще и еще. Кроме того, скорость имеет накопительный эффект. В Яндексе подобные фичи выкатываются десятками в день, и если не бороться за каждую долю, недолго докатиться и до 10%.

История кеширования в LS

Эта история связана с тем, что мы инлайним в страницу много статического контента.

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

Класть все в localStorage и при последующих входах на страницу подгружать оттуда, а не передавать по сети. Однажды мы решили, что удачной идеей будет использовать хранилище браузера.

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

Но при том улучшилось время до отрисовки шапки, до начала парсинга контента и до инициализации JavaScript. Среднее время доставки HTML (ключевая на момент разработки оптимизации метрика) увеличилось на 12%, что очень много. Процент по нему небольшой — 0,6, но если вспомнить о масштабах… Также сократилось время до первого клика.

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

Из этого можно сделать следующие выводы:

Во-первых, скорость действительно влияет на бизнес и бизнес-метрики.

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

В скорости так делать не надо. Когда-то Эш из зловещих мертвецов учил нас сначала стрелять, потом думать или вообще не думать.

Например, размер HTML и время его доставки — плохие метрики скорости, потому что пользователь не сидит с devTools и не выбирает сервис с меньшей задержкой. И третий момент: измерения должны отражать пользовательский опыт. А вот какие метрики хорошие и правильные — расскажем дальше.

Что и как измерить?

Измерения стоит начать с нескольких ключевых метрик, которые, в отличие, например, от размера HTML, близки к пользовательскому опыту.

Если TTFCP (time to first contentfull paint) и TTFMP (time to first meaningful paint) обозначают время до первой отрисовки контента и время до отрисовки значимого контента, то третью — время до инициализации фреймворка, стоит пояснить.

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

И последняя, четвертая метрика, время до первой интерактивности, обычно обозначается как time to interactive (TTI).

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

Time to firstcontentfull paint

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

Пока поддерживается два типа событий: first paint — любая отрисовка и firstcontentfull paint — любая отрисовка контента, отличного от белого фона пустой вкладки и контента бэкграунда страницы. Таким вызовом мы получаем набор событий об отрисовке.

Так мы получаем массив событий, фильтруем firstcontentfull paint и отправляем с неким ID.

Time to first meaningful paint

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

В Яндексе есть две школы костылей по измерению этой метрики: использование RequestAnimationFrame и измерение с помощью InterceptionObserver.

В RequestAnimationFrame отрисовка измеряется с помощью интервала.

Здесь это div с классом main-content. Допустим, есть некий значимый контент. Перед ним размещается скрипт, где дважды вызывается RequestAnimationFrame.

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

Первым идет выполнение JavaScript, затем — разбор стилей, потом расчет Layout, отрисовка и композиция.

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

Видно, что в конце первого из них отрисовался контент. Расположим два кадра рядом. Таким образом получаем интервал от JavaScript, вызванного в том кадре, где рендерился контент, и до JavaScript во втором кадре. Записываем нижнюю границу RequestAnimationFrame, вызванного внутри первого callback, и вызываем callback во втором кадре.

InterceptionObserver

Наш второй костыль с тем же контентом работает по-другому. На этот раз скрипт помещается ниже. В нем мы создаем InterceptionObserver и подписываемся на domNode.

Это время и записываем как точное время отрисовки. При этом дополнительных параметров не передаем, поэтому измеряем его пересечение с viewport.

Этот API был разработан, чтобы точно знать, когда пользователь увидел рекламу, но наши исследования показали, что на нерекламных блоках это тоже работает. Это работает потому, что пересечением основного контента и viewport считается именно то пересечение, которое видит пользователь.

Из двух этих способов все же лучше использовать RequestAnimationFrame: его поддержка шире, и он лучше проверен нами на практике.

JS Inited

Представьте некий фреймворк, у которого есть некое событие «init», на которое можно подписаться, но помните, что на практике JS Inited — одновременно и простая, и сложная метрика.

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

Time to Interactive

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

Измерить это помогает концепция долгих (длинных) задач и Long Task API.

Сначала о долгих задачах.

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

При этом фреймворк уже может быть проинициализирован, а кнопочки будут работать, но медленно. Пользователю придется дождаться, пока они закончатся, и только после браузер поставит на выполнение обработку его ввода. Момент, когда завершен последний Long Task и поток продолжительное время пустует, на иллюстрации наступает на 7 секундах и 300 миллисекундах. Такой отложенный ответ — довольно неприятный пользовательский опыт.

Как измерить этот интервал внутри JavaScript?

Здесь создается PerformanceObserver, который подписан на событие Long Task. Первый шаг условно обозначен как открывающийся тег body, после которого идет script. Внутри callback PerformanceObserver информация о событиях собирается в массив.

Он условно обозначен как закрывающийся тег body. После сбора данных наступает время для второго шага. Мы берем последний элемент массива, последнюю долгую задачу, смотрим на момент окончания ее выполнения и проверяем, достаточно ли прошло времени.

Нам оказалось достаточно 3 секунд. В оригинальной работе по этой метрике в роли константы взяты 5 секунд, но выбор никак не обоснован. Если проходит 3 секунды, мы засчитываем время до первой интерактивности, если нет, делаем setTimeout и проверяем на эту константу повторно.

Как обработать данные?

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

Мы передаём данные определённой метрики на специальную ручку на бэкенде и собираем их в хранилище.

Тут же приведены основные агрегации, которые мы обычно считаем по скоростным метрикам: среднее арифметическое и группа процентилей (50-й, 75-й, 95-й, 99-й). Здесь агрегация данных условно обозначена как SQL-запрос.

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

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

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

Чтобы точно не пропустить важную проблему, мы используем среднее арифметическое. Но та же чувствительность — преимущество, когда речь идет о мониторинге. Лучше переглядеть, чем недоглядеть. Оно легко смещается, но риск ложного срабатывания в данном случае — не такая уж большая проблема.

75% запросов укладываются в это время, его мы принимаем за оценку общей скорости. Кроме того, мы считаем медиану (если привязывать это к временным метрикам, медиана — показатель времени, в которое укладывается 50% запросов) и 75-й процентиль. Это очень большие числа. 95-й и 99-й процентили считаются для оценки длинного медленного хвоста. На 99-й процентиль приходятся аномальные показатели. 95-й рассматривается как самый медленный запрос.

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

Посчитав агрегации, остается только применить эти числа, и самое очевидное, что с ними можно сделать — представить в виде графиков.

Синяя линия отражает динамику для десктопов, красная — для мобильных устройств. На графике наши реальные метрики time to first contentfull paint для поиска.

За графиками скорости приходится постоянно следить, и мы поручили эту задачу роботу.

Мониторинг

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

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

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

Красный — среднее плюс три стандартных отклонения. Желтый график — показатель метрики, а синий — скользящее среднее с достаточно большим периодом. Зеленое —  то же самое, только со знаком минус.

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

Проверка фичей на скорость

Все, о чем шла речь, — работа со скоростными данными уже запущенного проекта, но хочется измерять скорость отдельных фич перед тем, как отправить их в большой продакшн. Для этого мы используем A/Б тестирование — сравнение метрик по контрольной и экспериментальной группам.

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

Здесь мы видим дельту и, чтобы точно определить, случайность это или значимый результат, применяется статистический тест. В A/Б тестировании, как правило, также используется среднее арифметическое.

С его помощью вычисляется так называемый «процент правоты». Он обозначен как «MW», потому что при расчете используется критерий Манна-Уитни. Здесь он установлен в 99,9%. У этого показателя есть порог, после которого мы принимаем дельту за верную.

Мы называем это прокраской. Когда тест достигает этого значения, дельта отмечается в интерфейсе цветом. Time to first meaningful paint не дотягивает до этого значения, то есть дельта тоже хорошая, но не 99,9%. Здесь мы видим зеленую, то есть хорошую прокраску на time to first contentfull paint. По инициализации фреймворка и time to interactive наблюдается уверенно плохая красная прокраска. Доверять ей полностью нельзя. Из этого можно сделать такой же вывод, как и в случае со шрифтами.

Как сделать у себя?

Реализовать измерения скорости у себя можно двумя способами. Первый — сделать все свое.

Ручка для приема данных с клиентов, backend, который складывает все это в базу, MongoDB, PostgreSQL, MySQL, любая СУБД (в них из коробки есть агрегации), плюс одно из множества опенсорсных решений — для того чтобы нарисовать графики и устроить мониторинг.

На примере «Яндекс Метрики» это выглядит так. Второе решение — воспользоваться системами аналитики «Яндекс Метрика» или Google Analytics.

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

Заключение

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

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

Если вам понравился этот доклад с HolyJS 2018 Piter, вероятно, вам будет интересно и на приближающейся HolyJS 2018 Moscow, которая пройдёт 24-25 ноября. Объявление напоследок. А уже завтра, с 1 ноября, цены на билеты повысятся до финальных, так что сегодня последняя возможность купить их со скидкой! Там можно будет не только увидеть многие другие JS-доклады, но и расспросить после доклада любого спикера в дискуссионной зоне.

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

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

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

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

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