RUM и полевое профилирование: лаборатория врёт, поле решает
Подключите onLCP, onINP, onCLS к маяку с роутом, релизом и классом устройства — без этих измерений полевые данные ни на что не отвечают. Атрибуция INP делит задержку входа, обработку и отрисовку. Семплируйте React Profiler на 1 проценте, алерты по p75 на кольцо релиза, без PII.
Вопрос был простой: «Какой релиз замедлил чекаут?» Ответ занял три недели. У команды были маяки vitals — значение, имя метрики, таймстамп — и четырнадцать деплоев в подозрительном окне. Ни тега релиза на маяке, ни роута, ни класса устройства. Они коррелировали таймстампы деплоев с часовыми агрегатами INP, получили трёх правдоподобных подозреваемых, переразвернули каждого по отдельности на staging-кольцо без живых пользователей, не воспроизвели ничего — и наконец нашли виновника бисект-редеплоями продакшена за четыре ночи: бамп зависимости дейтпикера, удвоивший время обработки ввода, но только на среднебюджетных Android — поэтому staging на ноутбуках M-серии оставался зелёным, а Lighthouse печатал 96. После инцидента в маяк добавили три поля: роут, релиз, класс устройства. Следующий вопрос «какой релиз регрессировал» стал одним запросом и занял тридцать секунд. Лаборатория говорит, что может быть медленным; только поле говорит, что медленно на самом деле, у кого и с какого момента.
Лаборатория врёт, поле решает
Прогон Lighthouse на вашей машине — одна выборка из распределения железа, в котором ваши пользователи не живут. Когда вы спорите с полевыми данными, прикрываясь Lighthouse-оценкой, — это именно та путаница, которую разбирает этот урок. CPU медианного пользовательского устройства в разы медленнее ноутбука разработчика; поле добавляет задушенные сети, телефоны с 4 ГБ RAM, режим энергосбережения, двадцать открытых вкладок, блокировщики рекламы и сессии по восемь часов вместо лабораторных девяноста секунд. Лабораторные инструменты (Lighthouse, WebPageTest, локальный Profiler) остаются необходимыми — они контролируемы, а значит, ловят регрессии до релиза на фиксированном устройстве и воспроизводят их детерминированно. Но они отвечают на вопрос «может ли это быть медленным?», и никогда — «медленно ли это у настоящих пользователей?». Полевые данные — Real User Monitoring (RUM, мониторинг реальных пользователей) — это та истина, на которой построена программа Core Web Vitals: Chrome агрегирует LCP, INP и CLS реальных сессий по origin, и проходной балл — p75 реальных просмотров страниц. Senior-команда использует оба мира и не путает их роли: лаборатория — пререлизный регрессионный гейт, поле — вердикт.
Подключение web-vitals: три измерения или шум
Библиотека web-vitals — стандартный инструмент: колбэки onLCP, onINP, onCLS срабатывают, когда метрика финализируется, — а для INP и CLS это поздняя стадия жизненного цикла страницы, поэтому отправка обязана сбрасываться на visibilitychange/pagehide через sendBeacon, а не на load. Сама проводка — двадцать строк; данные делают пригодными к действию измерения, которые вы прикрепляете:
import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
function send(metric) {
navigator.sendBeacon('/vitals', JSON.stringify({
name: metric.name, // 'INP' | 'LCP' | 'CLS'
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
route: routePattern(), // измерение 1: '/orders/:id', НЕ сырой URL
release: __RELEASE__, // измерение 2: id сборки, внедрённый при деплое
device: deviceClass(), // измерение 3: грубая корзина: память + ядра
attr: summarize(metric.attribution),
}));
}
onINP(send); onLCP(send); onCLS(send);Роут говорит, какая поверхность регрессировала, — глобальное число усредняет маркетинговую страницу с дата-гридом до бессмыслицы. Релиз превращает «когда-то стало медленно» в «кольцо N на 40% хуже кольца N−1» — вопрос о регрессировавшем релизе становится одним group-by. Класс устройства отделяет «мы выкатили регрессию» от «ретейл-кампания привела трафик с дешёвых Android» — сдвиг структуры трафика двигает глобальные перцентили без единого изменения кода. Отсутствие любого из трёх схлопывает разные популяции в среднее, неспособное отвечать на вопросы; это и есть трёхнедельная охота из вступления.
Глобальный p75 INP на дашборде — 180 мс, зелёный. Поддержка продолжает эскалировать лаги страницы поиска у пользователей Android. Что не так с измерением?
Атрибуция INP: куда ушли миллисекунды
INP — это примерно худшее взаимодействие просмотра страницы (для насыщенных страниц — высокий перцентиль всех взаимодействий, так что один плохой клик среди сотен всё равно считается). Attribution-сборка делит каждое взаимодействие-кандидат на три фазы, и это деление решает, где работать: задержка входа (input delay) — событие лежало в очереди, потому что главный поток был занят чем-то другим (гидрация, обновление чарта, сторонний скрипт); длительность обработки (processing) — выполнялись ваши обработчики, включая синхронный рендер и коммит React, который они запускают; задержка отрисовки (presentation) — кадр после обработчиков шёл долго (layout, paint, слишком много DOM). Атрибуция также называет целевой элемент и тип события. React-специфичное чтение: толстый слой processing на нажатиях клавиш — это обычно большой синхронный каскад рендера, и лечится он мемоизацией, виртуализацией списков или переносом обновления в transition. Толстая задержка входа на кликах сразу после навигации — это обычно гидрация или парсинг данных, оккупировавшие поток, и оптимизация обработчика там не даёт ничего. Обсерверы Long Tasks и более новый Long Animation Frames (PerformanceObserver с типом long-animation-frame) атрибутируют занятый поток конкретным скриптам, замыкая вопрос «занят чем?».
React Profiler в продакшене, по сэмплу
Колбэк onRender компонента Profiler отдаёт тайминги по коммитам: id, phase (mount/update), actualDuration (время рендера поддерева в этом коммите), baseDuration (оценка полного рендера без мемоизации). Продакшен-сборки по умолчанию вырезают профилирование; включение означает поставку profiling-сборки (react-dom/profiling) с накладными расходами в несколько процентов. Дисциплина, делающая это жизнеспособным: семплируйте сессии, а не коммиты — включайте профилирование примерно для 1% сессий, выбранных на старте сессии, отправляйте агрегированный actualDuration по id профайлера и тегируйте данные каждой сессии теми же тремя измерениями. Полная трассировка каждой сессии слишком тяжела дважды: рантайм-надбавка ложится ровно на те медленные устройства, которые вам важны, а объём маяков стоит дороже, чем даваемое им знание. Один процент от миллионов сессий даёт плотные перцентильные оценки по кольцам релиза — достаточно, чтобы увидеть, что коммиты OrdersTable стали в 3 раза медленнее в релизе 142, не профилируя ни одного лишнего пользователя.
▸Почему это работает
Почему p75, а не p50 или p99? p50 объявляет победу, пока четверть пользователей страдает, — «половине трафика нормально» это низкая планка. В p99 доминирует то, что вы не почините: умирающие батареи, фоновые вкладки, антивирусные сканы, роуминг на 2G — погоня за ним сжигает кварталы на шум. p75 — осознанный компромисс, стандартизованный программой Core Web Vitals: улучшив его, вы доказуемо улучшили опыт большинства пользователей, а цель остаётся достижимой и атрибутируемой вашему коду. Команды с насыщенными интерактивными приложениями часто ведут p95 внутренне как серию раннего предупреждения — но алерты и цели ставят на p75.
Перцентили по кольцам, алерты по ущербу и граница приватности
Запрос, отвечающий «какой релиз регрессировал»: p75 INP по роуту, сгруппированный по кольцу релиза, кольцо к кольцу — канарейка на 1% даёт ответ до полного раската, если трафик пробивает минимальный размер выборки. Дизайн алертов следует одному принципу: будить из-за ущерба пользователям, а не из-за метрик. Алерт, достойный пейджера, — конъюнкция: p75 роута выше порога И держится N минут И трафик выше пола — потому что каждое условие поодиночке производит шум: перцентили на тонком трафике дико болтаются, мгновенные всплески самоизлечиваются, а один медленный синтетический проб — не инцидент. Мягкий дрейф (p75 ползёт на 5% в неделю) уходит в канал еженедельного ревью, а не в пейджер. И граница приватности не обсуждается: перф-маяки не несут никакого PII — паттерны роутов вместо сырых URL (квери-строки текут токенами и почтами), грубые корзины устройств вместо отпечатков, никаких id пользователей. Перф-маяк, способный идентифицировать пользователя, — это инцидент по защите данных, ждущий своего аудитора; проектируйте схему так, чтобы вопрос не возникал вовсе.
Атрибуция INP для кликов по строке заказов: задержка входа 420 мс, обработка 35 мс, отрисовка 20 мс. Команда планирует спринт мемоизации пути обработчика клика. Что на самом деле говорит атрибуция?
- 01Назовите три измерения, нужные каждому vitals-маяку, и какой вопрос открывает каждое из них.
- 02Расшифруйте измерение INP: дайте три фазы деления атрибуции и отдельное лечение, на которое указывает каждая.
Лабораторные и полевые инструменты отвечают на разные вопросы, и senior-команда никогда не меняет их роли местами: Lighthouse и локальное профилирование — контролируемые детекторы регрессий, говорящие, что может быть медленным; Real User Monitoring — вердикт о том, что медленно, у кого и с какого момента, поэтому Core Web Vitals определены на полевых данных как p75 реальных просмотров. Инструмент — библиотека web-vitals: onLCP, onINP, onCLS со сбросом через sendBeacon на pagehide, потому что INP и CLS финализируются поздно, — а ценность данных решают три измерения маяка: паттерн роута (какая поверхность), релиз (какой деплой — p75 кольцо к кольцу делает вопрос о регрессировавшем релизе одним запросом) и класс устройства (регрессия против сдвига структуры трафика). INP читается через деление атрибуции: задержка входа — борьба за главный поток с чужой работой, и указывает на атрибуцию Long Task/LoAF; обработка — ваши обработчики плюс запущенный ими синхронный рендер, и указывает на мемоизацию или transitions; отрисовка — стоимость пейнта, и указывает на размер DOM. React Profiler работает в продакшене через profiling-сборку с маяками onRender, но семплированно — около 1% сессий: полная трассировка облагает налогом ровно те медленные устройства, что вам важны, и топит вас в маяках, а семплированные перцентили по кольцам всё равно точно показывают трёхкратную регрессию коммитов. Алерты будят по ущербу пользователям и никогда по одиночным метрикам: порог И стойкая длительность И пол трафика, а медленный дрейф уходит в ревью, не в пейджер. Граница приватности структурна: паттерны роутов вместо URL, грубые корзины устройств, никаких id — перф-маяк, способный идентифицировать пользователя, это инцидент, который вы запланировали на потом. Теперь, когда встретишь жалобу на лаг, которую Lighthouse не подтверждает, — тяни первым делом трёхмерный запрос: роут, релиз, класс устройства; ответ почти всегда в сегменте, который глобальное среднее скрывало.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.