Солидные фронтенды: мониторинг

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

Солидные фронтенды: мониторинг

Node.js

Технически, Node.js — это, конечно, не фронтенд. Но так сложилось, что огромная часть Node.js-приложений создаётся и поддерживается фронтенд-инженерами — SSR, BFF.

К счастью, мониторинг Node.js-приложения не сильно отличается от мониторинга любого другого серверного приложения. Исключение — пара специфичных для платформы показателей.

Состояние сервера

Наши приложения работают на серверах (даже если это serverless-приложения) и важно отслеживать состояние этих серверов. Две самые важные для нас метрики — потребление памяти и использование процессора.

Конкретная методика сбора этих метрик зависит от способа эксплуатации серверов, лучше обсудить это с админами компании. Если их не существует — разбираться как администрировать сервера, это тема для отдельной статьи.

Внутреннее состояние Node.js

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

Другие метрики:

  • Метрики сборщика мусора — время затраченное на сборку мусора, количество циклов сборки мусора, объём освобождённой памяти. Эти метрики помогают обнаружить проблемы с сомнительными алгоритмами, создающими слишком много мусорных объектов. На практике, они редко оказываются полезнее, чем внешние показатели потребления памяти.
  • Метрики состояния сервера — объём доступной памяти, объём использованной памяти, время процессора. Эти метрики помогают обнаружить утечки памяти. На практике, они менее точные, чем метрики собираемые с сервера, на котором исполняется Node.js-приложение.
  • Метрики открытых ресурсов — количество открытых файловых дескрипторов, количество активных libuv-хэндлов. Эти метрики помогают находить проблемы с зависшими ресурсами.

libuv — это Clang-библиотека, реализующая асинхронное IO в Node.js. Число открытых хэндлов характеризует количество асинхронных контекстов.

Для получения всех этих метрик в Node.js есть удобные API. Например, вот так в prom-client рассчитывается лаг ивент-лупа.

Почитать подробнее про мониторинг внутреннего состояния Node.js-приложения можно в статье «Мониторинг Node.js-приложения».

Состояние веб-сервера

Чаще всего Node.js-приложение — это веб-сервер, обслуживающий запросы клиентов. В таком случае, нужно отслеживать насколько успешно ему это удается. Самые популярные метрики:

  • Длительность обработки запроса — обычно собирают данные по бакетам — сколько запросов заняло от 5 до 10 мс, сколько от 10 до 20 и так далее.
  • Количество не-200 ответов — количество статусов ответа конечно, поэтому можно собирать эти числа напрямую — сколько запросов завершилось с кодом 200, сколько с 201, сколько с 400 и так далее.

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

Если приложение переживает стрессовую нагрузку и лаг ивент-лупа растет, замеры длительности обработки запроса перестанут быть правдивыми. Например, если лаг достиг 100 мс, флоу обработки запроса будет выглядеть примерно так:

При этом, силами приложения длительность будет вычислена как 20 мс, но для клиента запрос займет 120 мс.

Аналогичная ситуация с подсчетом статусов ответов. Если произойдет какая-то исключительная ситуация (например, физически выйдет из строя участок памяти), мы не можем гарантировать, что приложение корректно отчитается о нем.

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

Из коробки nginx-ingress умеет отдавать метрики в Prometheus, а Traefik — в Prometheus, DataDog, StatsDB, InfluxDB.

В Авиасейлс есть мониторинг статусов ответов при раздаче статических ресурсов — стилей, шрифтов, картинок и скриптов. Почти всегда на графике видны только ответы со статусом 200 — все идёт по плану. Но после очередного деплоя я заметил там резкий всплеск ошибок 404. Сервер раздачи статики не мог найти запрашиваемые файлы.

В логах я обнаружил запросы за ресурсами вида /font.hash.waff2 url(.... Оказалось, что в релизе случайно удалили закрывающую скобочку при указании адреса шрифта и браузер шёл за ним по неправильному пути.

Специфичные для приложения метрики

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

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

  1. Сколько времени занимает рендер конкретного виджета. По этой метрике мы находим «тяжелые» виджеты и решаем что с ними делать.
  2. Сколько каких виджетов рендерится. Тут мы смотрим, насколько равномерная нагрузка приходится на сервис, не слишком ли много мы рендерим «тяжелых» виджетов.

Фронтенды

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

Ошибки

Первое, что нужно начать собирать с клиентов — ошибки. В индустрии существует много классных сервисов, которые решают эту задачу:

  1. Sentry — магический комбайн, который умеет не только собирать ошибки, но и сразу строить отчеты по производительности фронтенда. Классная штука.
  2. Rollbar — более узкоспециализированная система, предназначенная именно для сбора ошибок.
  3. ...десятки других сервисов

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

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

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

Перформанс метрики

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

Большую часть перформанс метрик можно собирать с помощью Sentry, о котором я упоминал в разделе про ошибки. Магический инструмент!

Core Web Vitals

Перформанс метрик много, но самые важные из них пару лет назад Google зафиксировал и назвал Core Web Vitals. Давайте по порядку разберёмся, что это за метрики и как их можно добыть.

Largest Contentful Paint показывает через какое время после загрузки страницы случится отрисовка самого большого блока. Говорят, что следует держать ее в пределах 2,5 секунд.

First Input Delay — это время от момента, когда пользователь провзаимодействовал со страницей, до момента, когда приложение обработало его ввод. Например, пользователь нажал на кнопку смены темы, через 200 мс страница перекрасилась — FID равен 200 мс. Это очень важная метрика, для хорошего опыта использования приложения нужно держать ее в пределах 100 мс.

Cumulative Layout Shift отражает визуальную стабильность страницы — прыгает ли текст после подгрузки шрифтов, дергается ли раскладка после загрузки изображений и так далее. Метрика измеряется в абстрактных попугаях и лучше не позволять ей подняться выше 0,1.

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

Сбор в лаборатории подразумевает запуск специального браузера в контролируемом окружении (всегда на одной и той же машине, с одним и тем же интернетом) и измерении метрик внутри него. Это самый удобный способ, он не затрагивает пользователей и даёт наиболее стабильные значения метрик.

Сбор в поле — это работа с живыми пользователями. В этом случае предлагается в браузер пользователю загрузить небольшую JS-библиотечку, которая будет измерять показатели и отправлять их на сервер. Этот способ менее удобен — нужно создавать инфраструктуру принятия метрик из браузера, нагружать пользовательские устройства дополнительной работой. Плюс метрики получаются очень «шумными» из-за того, что клиенты используют разные устройства и у них разное качество соединения. Зато итоговые цифры лучше всего отражают ощущения реальных пользователей сайта. Реализовать это можно примерно так:

import {getCLS, getFID, getLCP} from 'web-vitals';

function sendToAnalytics(metric) {
  // Код отправки данных на сервер
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

Кроме самостоятельного сбора Core Web Vitals метрик, можно обратиться к публичным сервисам, которые предоставляют отчеты по ним в удобном виде: Chrome User Experience Report, PageSpeed Insights, Search Console — Core Web Vitals report.

Другая Web Vitals метрика

Ещё один довольно важный показатель, который может сильно изменить ощущения от использования сайта — Time to First Byte — это время, которое проходит с момента запроса до начала ответа.

В случае, если речь идёт о статическом сайте (без SSR), она показывает как быстро сервер начинает отдавать файлы. При рендеринге приложения на сервере эта метрика показывает, как быстро отрабатывает вся цепочка обработки запроса. Если время рендеринга (которое замеряется в Node.js-приложении) небольшое, а TTFB высокий — время теряется где-то между клиентом и рендерингом: на прокси-серверах, при установке SSL-соединения или где-то еще.

Весной 2020 года мы настроили непрерывный мониторинг перформанс-метрик Авиасейлс и обнаружили неожиданно высокий TTFB. На тот момент главная страница была заранее срендеренной статикой, которая раздавалась nginx-ом. Мы точно знали, что запрос после попадания на сервер сразу приходит в этот nginx и не понимали куда тратится время. После более тщательного исследования, мы обнаружили, что из 560 мс больше 200 уходит на установку SSL-соединения. Оказалось, использующийся на нашем сайте сертификат с Extended Validation требует нескольких лишних запросов для валидации, а из бонусов даёт только красивую надпись в адресной строке некоторых браузеров. Мы заменили его на обыкновенный и получили TTFB 305 мс.

Размеры бандлов

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

Если в приложении нет подключаемых из внешнего мира ресурсов, размер бандлов можно легко контролировать еще до попадания кода на продакшн. Например, можно настроить size-limit и предотвращать вливание ПРов, которые слишком сильно раздувают бандлы. Или как минимум, вливать их осознанно.

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

Мы написали небольшой Node.js-сервис, который запускает Chrome, заходит через него на сайт и замеряет, сколько и каких ресурсов скачалось. Все ресурсы сортируются по типу (скрипты, стили, шрифты, картинки) и владельцу (наши или не наши). В итоге мы видим на графиках, если кто-нибудь добавляет в GTM новый большой скрипт и сильно замедляет работу сайта.

Хранилище метрик

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

Все наши метрики отправляются в Prometheus и хранятся там три месяца. Данные из Node.js-приложений Prometheus забирает сам, мы просто предоставляем внутренний эндпоинт, который отдаёт их в специальном формате. Для удобства используем prom-client. Ошибки клиентских приложений мы отправляем в Rollbar, а чтобы направить их оттуда в Prometheus написали небольшой сервис, который забирает ошибки из Rollbar API. Метрики производительности мы измеряем только в лаборатории — специальный сервис запускает Chrome, собирает данные и отдаёт их в Prometheus.

В итоге все метрики наших фронтендов лежат в одном месте и мы можем строить классные графики в Grafana.

Алерты

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

Мы используем Opsgenie, условия срабатывания алертов описаны в специальном файле, который лежит в репозитории. У каждого алерта есть уровень важности — warning и critical. Ворнинги просто приходят в канал в слаке и на них реагирует дежурный инженер в порядке очереди. Критикалы тоже приходят в слак, а если дежурный не среагирует в установленное время, то система (на самом деле дежурный админ) заботливо позвонит по телефону и поможет проснуться даже ночью. Мы считаем, что критикал проблемы важнее, чем здоровый сон.

Резюме

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


Пишите комментарии в телеграм-канал или в твиттер 🤗