Привет! Меня зовут Андрей Якобчук, я ведущий фронтенд-разработчик в Muse Group. Мы постоянно работаем над ускорением клиентской части наших сайтов. К тому же Гугл с его метриками Core Web Vitals с каждым годом придаёт всё большее значение отзывчивости и стабильности интерфейса сайтов и понижает в ранжировании те, которые считает неудобными для пользователя. В статье я расскажу о том, какие подходы мы используем для измерения и мониторинга перфоманса сайтов, а также дам рекомендации, как можно улучшить ваш проект. 

Зачем ускорять, если всё и так работает

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

Удобство для пользователя

Важная характеристика интерфейса для пользователя — естественность изменений при взаимодействии с ним. Мы говорим об отзывчивости интерфейса, которая ограничена возможностями восприятия человека. Ещё в 1993 году UX-исследователь Якоб Нильсон в книге Usability Engineering описал, когда человек способен воспринимать конкретные внешние изменения как естественные.

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

При клике на кнопку reply мы показываем форму ответа на комментарий. В данном случае нам нужно её просто показать.
При клике на кнопку reply мы показываем форму ответа на комментарий. В данном случае нам нужно её просто показать.

В примере на картинке могут возникнуть проблемы. Например, если основной поток занят другими задачами, задача по показу формы ответа может выйти за 0,1 секунды. Гугл рекомендует вписывать такие взаимодействия в 50 мс, чтобы оставить зазор на случай, если основной поток будет забит другими задачами. 

1 секунда. Это предел для непрерывного потока мыслей пользователя. Даже если он заметит задержку, он не успеет отвлечься. Обычно обратная связь здесь тоже не требуется, но пользователь уже теряет ощущение непрерывной работы с данными и интерфейсом.

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

Анимации можно делать 100 мс и меньше, конечно. Но при этом пользователю будет сложно воспринять изменения. Это лишь увеличит когнитивную нагрузку при взаимодействии с интерфейсом. Есть правило: чем больше часть интерфейса, которую мы анимируем, тем более длинная анимация нужна. Например, при hover на кнопку будет достаточно 150 мс, а для анимации показа модального окна уже потребуется 300–500 мс.

10 секунд. Это предел для удержания внимания пользователя. Он может захотеть переключиться на другую задачу. В этом случае важно дать обратную связь: показать, что задача действительно длинная, и пользователь может вернуться к ней позже.

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

16 мс — это важный промежуток времени для анимации и скролла интерфейса. Если каждый фрагмент анимации укладывается в промежуток 16 мс (60 fps), то пользователь воспринимает эту анимацию как плавную. Также важно учитывать, что часть времени уйдет на непосредственную отрисовку кадра, так что на все наши расчеты мы можем потратить до 10 мс. Нас могут сильно ограничивать другие задачи которые выполняются параллельно в нашем коде.

Что можно сделать, если мы не укладываемся в эти рамки

К сожалению, загрузка страницы за 1 секунду при холодном старте приложения — довольно сложная задача, так как общение с сервером вносит свои задержки. По реальным данным время ответа сервера (TTFB) составляет 500–600 мс на десктопе, а на мобильных устройствах, даже при хорошем соединении, может доходить до 1 секунды и более. К тому же оно не всегда одинаково, поэтому Гугл рекомендует укладываться в 2 секунды на хорошем соединении 4G и лучше, а при 3G не зазорным будет уложиться в 5 секунд.

Если изменение происходит в рамках текущей страницы, либо при загрузке страницы мы не укладываемся в 1–2 секунды, то можно показать лоадер в том месте, где появится интерфейс. Более современный способ — показать контентный плейсхолдер на месте UI, который должен подгрузиться. Такой вариант особенно хорош, если блоки интерфейса имеют стандартизированные размеры.

Youtube показывает плейсхолдер вместо UI, пока грузится вся графика, данные и бизнес-логика.
Youtube показывает плейсхолдер вместо UI, пока грузится вся графика, данные и бизнес-логика.

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

Прогресс бар при загрузке скора
Прогресс бар при загрузке скора

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

Такое письмо получает пользователь, когда загрузка скора завершена
Такое письмо получает пользователь, когда загрузка скора завершена

Интересы бизнеса и технические ограничения продукта

Если смотреть на ускорение работы сайта с точки зрения бизнеса, то наша задача — получить результат минимальными усилиями за минимальные вложения. Поэтому мы приняли решение отказаться от SSR. Вся отрисовка сейчас происходит на клиенте. Это позволяет нам:

  1. Держать на 30% меньше серверных мощностей.

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

  3. Уменьшить технологический зоопарк.

С другой стороны это увеличивает требования к клиентскому коду.

Требования Гугла

Гугл давно уже стоит на страже перфоманс, а после апдейта в августе мы все живем в рамках новых метрик — Core Web Vitals.

  1. Теперь Гугл учитывает не только скорость загрузки сайта, но и то, насколько он отзывчивый и насколько интерфейс стабилен.

  2. Все метрики Гугл собирает с реальных пользователей в реальном времени. Сайт теперь оценивается на основе средней скользящей за последние 28 дней.

  3. Есть четкий performance budget — 70+% заходов по 75-му перцентилю на страницы домена должны быть в зеленой зоне по каждой из метрик (каждая метрика имеет красную, желтую и зеленую зону).

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

Инструменты аналитики

Здесь я не буду рассказывать про такие стандартные браузерные инструменты, как performance panel в браузерах, об этом уже много писали в различных источниках. Для всех современных фреймворков типа Angular, Vue и UI-библиотек типа React есть свои инструменты разработчика, в которых также можно измерять производительность работы конкретного технического решения. 

В инструментах разработчика Chrome также можно отслеживать Core Web Vitals
В инструментах разработчика Chrome также можно отслеживать Core Web Vitals

Вопрос performance — это, в первую очередь, вопрос баланса. Если не учитывать performance совсем, это негативно повлияет на бизнесовые метрики. Но в то же время — это бездонная бочка, в которую можно вложить бесконечное количество ресурсов. Соблюсти баланс в них нам помогает performance budget. В рамках performance budget мы устанавливаем приемлемые с точки зрения бизнеса SLO для наших перфоманс метрик. Здесь мы также придерживаемся Core Web Vitals, так как с одной стороны, эти метрики охватывают основные моменты отрисовки страницы, а с другой — позволяют нам хорошо индексироваться Гуглом, который является основным источником трафика.

Чтобы понимать, где мы находимся в рамках этого бюджета, необходим мониторинг. В данный момент у нас есть два типа аналитики:

  1. Наша внутренняя аналитика (realtime).

  2. Аналитика построенная на основе Google BigQuery

Realtime аналитика

После релиза наравне с Sentry смотрим дашборд перфоманса. Через 30 минут уже примерно видно, хорошо ли идет релиз. И если заметно, что метрики сильно выросли, то можно принимать решение об откате. Если нет видимого роста, то проверяем релиз через пару часов, чтобы быть уверенными, что все ок.

Данные собираются в ClickHouse, и на их основе мы строим графики в Superset. Данные агрегируем до 5 минут, но они на таком интервале очень шумные. Средствами Superset мы можем укрупнить агрегацию до часов или дней и построить смещение на день или неделю для удобного сравнения. Плюс возможность смотреть по релизам и сразу же определять, что что-то идёт не так. Можно сравнить день со днем или с прошлой неделей.

Здесь видно, что в релизе 1.17.19 что-то пошло не так.
Здесь видно, что в релизе 1.17.19 что-то пошло не так.

Дашборд, где показан общий тренд по дням, смотрим ежедневно.

Core Web Vitals для десктопных скоровых станиц
Core Web Vitals для десктопных скоровых станиц

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

Ещё у нас есть свои кастомные метрики, которые помогают при локализации проблем.

Здесь все завязано на то, как наши страницы грузятся. Сначала отрисовывается App Shell — измеряем Layout dom ready. Затем наступает время отрисовки контента страницы — Content dom ready. И лишь после этого случится LCP.
Здесь все завязано на то, как наши страницы грузятся. Сначала отрисовывается App Shell — измеряем Layout dom ready. Затем наступает время отрисовки контента страницы — Content dom ready. И лишь после этого случится LCP.

Про то, что такое App Shell можно почитать здесь

Аналитика Google BigQuery

Этот дашборд достаточно проверять раз в неделю.

По этой аналитике мы смотрим, как нас воспринимает Гугл. Важное отличие — Гугл показывает не абсолютные данные, а скользящее среднее за последние 28 дней. Это значит, что если вы сломали или починили что-то, то абсолютные метрики будут примерно такими же как и в realtime аналитике только через 28 дней. 

Абсолютное значение LCP на мобильной скоровой
Абсолютное значение LCP на мобильной скоровой

Следующий график на дашборде отражает распределение количества загрузок хороших страниц, страниц требующих доработки и плохих. Важно, чтобы загрузок в зелёной зоне (хорошие страницы) было больше 75%.

Google search consоle

C недавнего времени Гугл начал показывать в поисковой консоли качество сервиса с точки зрения перфоманса. Там можно посмотреть общую картину по домену (> 70% — это хорошо).

Процент урлов в зеленой зоне по домену
Процент урлов в зеленой зоне по домену

И указать на конкретные проблемы с сайта.

Провалившись в метрику, можно увидеть уже конкретные URL, которые Гугл считает требующими доработки.

Алерты

У нас есть алерты для перфоманс метрик, которые помогут в случае, если что-то пошло не так, даже если релиза не было. Ну и человеческий фактор никто не отменял. Есть суточные алерты на каждую метрику, а есть алерты за три часа, чтобы отлавливать «жесткие» скачки метрик в негативную сторону и не ждать сутки, чтобы начать решать проблему. 

Почему не Lighthouse и PageSpeed Insights

Мы сознательно перестали фокусироваться на использовании Lighthouse и PageSpeed Insights по двум причинам:

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

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

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

Оптимизируй это

Здесь я составил небольшой набор рекомендаций, которые помогут поддерживать сайт всегда быстрым.

Фокус на главном

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

Ограничения

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

  1. Ограничения по размеру чанков. Чанк с контентом страницы должен быть всегда один. Также на чанки стоит ограничение по размеру. Превышен размер чанка — деплоя нет.

  2. Ограничения по техническому стеку. Мы очень трепетно относимся к тому, сколько и какие внешние технологические решения мы привносим в продукт. Это важно, чтобы обходиться минимальным количеством решений, особенно это касается первого старта. Любое решение приносится в продукт открыто и только в результате исследования, в том числе сможем ли мы обойтись без этого решения. К слову, общий вес core libs у нас всего 27 КБ gzip.

Оптимизации

Мы фокусируемся на получении наилучшего значения Core Web Vitals как основных метрик производительности фронтенда.

Largest Contentful Paint (LCP) — скорость загрузки основного контента

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

  1. В месте, где вы управляете состоянием приложения, можно завести свойство, которое будет говорить, когда произошел LCP, и завязать на его изменение отрисовку не критичных с точки зрения первой отрисовки частей UI и non-UI функциональностей (например, аналитики).

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

  3. Старайтесь не выполнять никакую бизнес-логику до LCP, так как это вызовет выделение лишних процессорных ресурсов.

  4. Также важно понимать, что стабильность UI при его отрисовке также влияет на LCP. Если какие-либо другие элементы будут влиять на позицию вашего LCP-элемента на странице, то это отложит LCP.

  5. Вся below the fold графика должна грузиться через lazy load.

Cumulative Layout Shift (CLS) — совокупное смещение макета

Данный раздел важен только для блоков страницы, которые находятся во viewport либо будут загружены во viewport после взаимодействия пользователя. Важно также понимать, что CLS — метрика кумулятивная, то есть учитываются все прыжки интерфейса, которые были в процессе взаимодействия со страницей. Основные оптимизации можно посмотреть здесь.

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

После взаимодействия пользователя

  1. Вставка новых блоков должна быть в промежутке 500 мс после взаимодействия пользователя, тогда она считается естественной и любой layout shift не будет засчитываться в скор.

  2. Если не получается вставить блок за это время, то необходимо как-то резервировать место под блок.

  3. Если происходит трансформация элемента, например при скролле, то необходимо обязательно использовать CSS Transform для этой цели и анимировать именно через это свойство, так как в этом случае не будет происходить relayout.

Как можно резервировать место

  1. Просто выделить место, возможно, с лоадером.

  2. Делать контентные плейсхолдеры.

Общий совет —  у всех медиа-блоков (embed/video/image) должны быть зафиксированы размеры.

First Input Delay (FID) — время ожидания до первого взаимодействия с контентом

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

Итого

Мы оптимизировали наши основные сервисы, чтобы значения Core Web Vitals для их основных страниц всегда находились в зелёной зоне. Если значения Core Web Vitals ваших сервисов — в жёлтой или красной, вы знаете что делать. Помните, что метрики необходимо постоянно мониторить. К тому же Гугл обещает, что Core Web Vitals будут развиваться и дальше. Расскажите в комментариях, что вам помогает улучшать производительность сервисов. 

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


  1. yarkov
    21.01.2022 20:03
    +3

    пользователь загружает скор

    Каво он загружает?


    1. mapnik
      22.01.2022 00:56
      +3

      Юзер лоадит скор, какое слово тут непонятно?


    1. YakobchukAndrey Автор
      24.01.2022 00:11

      Ага, именно, скор) Это один из форматов цифрового предстваления sheet music


  1. geniot
    21.01.2022 23:48

    Здравствуйте, Андрей. Спасибо за интересный взгляд изнутри. Я давно знаком с MuseScore, теперь вот узнал, как вы работаете над производительностью. У меня вопросы, может быть, слишком приземлённые. Это вы делали плеер на сайте, например, https://musescore.com/user/12461571/scores/3291706/piano-tutorial ? Вы рассматривали вариант загрузки SVG, а не PNG? Когда плеер играет, что это звучит mp3, midi? Я посмотрел network, но уловить там формат тяжело. В то же время, если бы вы проигрывали midi, то вам нужно было бы загрузить music-font, который весит около 2Мб. А такого размера в network я не нахожу. Как происходит выделение тактов во время проигрывания? На сервере, я так понимаю, вы подготовили некий формат данных, чтобы потом отрисовывать такты на клиенте. Вот меня интересует, как нашли x,y для всех тактов. Какой подход использовали. Всё это я спрашиваю, потому что сам недавно реализовал похожий плеер, и мне интересно, как это у вас сделано и почему вы выбрали то или иное решение. Если это коммерческая тайна, то заранее прошу прощения. Провоцировать на нарушение NDA у меня не было цели.


    1. JiLiZART
      23.01.2022 13:08

      Везде SVG, играет mp3 или midi (через sound font) на выбор.


      1. geniot
        23.01.2022 15:03

        Нет вообще-то. Грузится вот этот PNG https://musescore.com/static/musescore/scoredata/g/8fbceb3e46eb0cf31b7f20922556d3f5ebcfa43d/score_0.png@0?no-cache=1621346013

        Выбора "mp3 или midi" нет на сайте как опции.

        Зачем вы вообще отвечаете, если не знаете? Или вы не вникли в вопрос. Я спрашивал про веб-плеер на сайте https://musescore.com/ , а не про open source продукт MuseScore.


        1. JiLiZART
          23.01.2022 22:55
          +1

          Так тому и быть. Хотел как лучше.

          PNG осталось у кучки старых скоров.

          Mp3 или Midi можно выбрать в микшере. (если долго искать можно найти)

          PS

          Веб плеер и Piano Roll за моим авторством.


          1. geniot
            23.01.2022 23:58

            А, раз так. Извините. И как же тогда вы отрисовываете такты на клиенте? Откуда берёте x, y координаты?


        1. YakobchukAndrey Автор
          24.01.2022 00:07

          Выбор mp3 или midi звучания как раз скрыт в микшере. Выбирая Synthesizer, вы будете слышать при проигрывании именно звук на основе midi.

          А, как ранее отметил @JiLiZART, формат графики для отображения скора (png или svg) будет зависеть от того, на какой версии MuseScore редактора был создан скор. Если на старой, то png. Вот, например, относитетельно новый скор https://musescore.com/user/37107790/scores/7081694 - тут уже svg. А а скоры с png картинками мы точно не будем торогать, так как при конвертации исходных файлов скоров сейчас будут артефакты, а для нас важнее, чтобы пользователь получил именно правильный контент


          1. geniot
            24.01.2022 00:20

            Ясненько. Это очень круто сделано. Я сам тут делаю обучающий плеер, типа flowkey, но для баяна и аккордеона. Поэтому меня вся эта тема интересует.


    1. YakobchukAndrey Автор
      24.01.2022 00:23

      Вот меня интересует, как нашли x,y для всех тактов

      Тут все просто:

      • Есть массив ноты (у каждого из инструментов свой массивчик будет);

      • У каждой ноты есть длительность и начало по времени.

      Всю эту информацию можно вытащить из самого midi файла

      Затем нам остается расположить наши ноты на канвасе на основе вышеописанных данных. Ну и раскраить эти ноты для каждого истремента и руки.

      Если Я где-то ошибся, то, думаю, @JiLiZART меня поправит, так как это его "детище")


      1. geniot
        24.01.2022 00:31

        Ого. Ну это очень сложно. Ведь это означает, что вы сами ноты рендерите? А в каком формате изначально ноты хранятся? Миди? Что-то я сомневаюсь... Скорее всего вы там какой-то движок используете LilyPond, Sibelius, Finale... А потом экспортируете в SVG и оттуда x, y выковыриваете.


        1. YakobchukAndrey Автор
          24.01.2022 00:47

          Нет, изначально все храниться в формате mscz (musescore editor). А из него и midi можно получить и mp3 и svg...


          1. geniot
            24.01.2022 00:59

            А! Всё, я понял. У вас же свой редактор есть. Т.е. свой рендерер. Ну тогда понятно. Я остановился на Sibelius, но для меня это не принципиально. Основные форматы SVG, PNG и MIDI.