open atlas
↑ К треку
Браузер и фронтенд-рантайм WEB · 02 · 08

Observability в проде: LoAF, INP и полная поверхность атаки

LoAF и INP в продовой телеметрии, скролл вне главного потока, display locking, reduced-motion, Web Workers, Service Workers, CI-тестирование и полная поверхность атаки пайплайна.

WEB Senior ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

Ваша страница набирает 95 в Lighthouse в офисе. На Pixel 4a пользователя в Джакарте с 3G-троттлингом INP — 340 мс. Это два разных измерения двух разных вещей. Только одно из них важно.

LoAF и INP: два взаимодополняющих сигнала

PerformanceLongAnimationFrameTiming (LoAF, Long Animation Frame — длинный кадр анимации; поставлено в Chromium в 2023–2024) сообщает о каждом кадре длиннее 50 мс с разбивкой того, что доминировало: время рендеринга, блокирующий JS, форсированная компоновка. Это диагностический инструмент — говорит что работало долго, а не что почувствовал пользователь.

INP (Interaction to Next Paint — взаимодействие до следующей отрисовки) измеряет время от пользовательского input-события (клик, клавиша, тап) до следующего paint, видимо реагирующего. INP стал Core Web Vital в марте 2024, заменив FID. Плохой INP (>200 мс p75) почти всегда восходит к одной из двух проблем пайплайна:

  1. Длинная JS-задача в input-обработчике, откладывающая rAF
  2. Форсированная синхронная компоновка от чтения геометрии внутри обработчика

LoAF даёт данные для атрибуции в продакшене; INP даёт метрику, которую чувствует пользователь. Вместе они замыкают петлю от продовой телеметрии к конкретным диагностикам пайплайна.

Путь диагностики INP

INP > 200 мс — пользователь воспринимает взаимодействие вялым

↓ исследуйте с помощью

LoAF — какой кадр, что доминировало (JS / компоновка / рендеринг)

↓ отображается в
Длинная JS-задача в обработчике → уступить через scheduler.yield() или разбить с setTimeout
Форсированная синхронная компоновка в обработчике → двухпроходный батч чтений/записей

Скролл вне главного потока

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

Браузеры поставляют скролл на потоке compositor последнее десятилетие: когда пользователь скроллит обычную страницу, compositor транслирует viewport на GPU без касания главного потока. Страница может скроллить плавно даже пока идёт долгая JS-задача.

Подводный камень: любой элемент с JS scroll-обработчиком, подключённым не-passively (без {passive: true}), заставляет браузер откатиться на скролл главного потока, потому что обработчик может вызвать preventDefault(). Всегда передавайте passive: true слушателям scroll, wheel и touchmove, если только вам действительно не нужен preventDefault. Современные браузеры предупреждают в DevTools, когда non-passive слушатель задерживает скролл.

Display locking и content-visibility

Механизм за content-visibility: auto — «display locking»: браузер приостанавливает рендеринг заблокированного поддерева (без пересчёта стилей, без компоновки, без отрисовки) и заменяет его placeholder intrinsic-size. Когда поддерево пересекает viewport через внутренний IntersectionObserver браузера, оно разблокируется и рендерится.

Числа: таблица из 10 000 строк, ранее стоившая 1 200 мс style + layout, рендерится за ~10 мс с content-visibility: auto.

Компромисс: скролл в ранее заблокированную область кратко останавливается для рендеринга. Если строки дорогостоящие, виден однокадровый рывок на границе разблокировки. Пары с contain-intrinsic-size даёте браузеру реалистичный placeholder-размер, чтобы позиция скролла не прыгала.

Reduced-motion как клапан бюджета рендеринга

@media (prefers-reduced-motion: reduce) устанавливается пользователями, испытывающими укачивание или желающими снизить энергопотребление. Помимо доступности, это клапан бюджета рендеринга: любую compositor-управляемую анимацию можно заменить мгновенным изменением состояния при reduced-motion, освобождая compositor от покадровой работы. Пользователи с ограниченным зарядом на бюджетном Android-телефоне получают значимую экономию батареи.

Web Workers и разгрузка главного потока

Когда главный поток достигает потолка, единственный способ снизить его — перенести работу. Web Workers выполняют JS в отдельном потоке без доступа к DOM; сериализация через postMessage стоит ~1 мс/МБ, что оправдано для CPU-тяжёлых задач (парсинг большого JSON, сжатие, криптография, обработка разметки).

OffscreenCanvas даёт прямой доступ к canvas API из воркера, полностью обходя главный поток. SharedArrayBuffer + Atomics предоставляют примитивы синхронизации между потоками для высокочастотных данных (аудио, потоки сенсоров реального времени). Стоимость входа высока (structured cloning, архитектура передачи сообщений), но потолок производительности пропорционально выше: четырёхъядерный телефон с занятым главным потоком всё ещё имеет 3 незадействованных ядра, которые большинство приложений никогда не используют.

Service Worker и пайплайн первой отрисовки

Service Workers не часть пайплайна рендеринга, но критически влияют на его входные данные. Service Worker, отвечающий на запросы из кэша (cache-first для статических ресурсов, network-first для API), доставляет первую отрисовку за 50 мс на повторных посещениях вместо 500 мс — 4 кадра против 30 при 60 fps.

Ключевое ограничение: скрипт Service Worker работает в отдельном потоке, но его запуск (когда он перехватывает первый запрос) имеет небольшую латентность. Держите Service Worker тонким и быстрым; 200-мс запуск при перехвате fetch задерживает первый HTML-байт и весь пайплайн парсинга вместе с ним.

Performance CI: перехват регрессий до мерджа

Бюджеты удерживаются, только когда регрессии перехватываются до мерджа. Реалистичный CI-пайплайн имеет три уровня:

  1. Lighthouse CI в headless Chrome на каждом PR — ограничивает по абсолютным метрикам (LCP < 2.5 с, INP < 200 мс, CLS < 0.1), блокирует мердж при регрессии.
  2. Синтетические бенчмарки на критических сценариях (открыть страницу, проскроллить список 1000×, нажать 5 кнопок) — измеряет p95 длительности кадра и p95 LoAF, сравнивает с baseline-веткой.
  3. RUM (Real User Monitoring — мониторинг реальных пользователей) в продакшене — отправляет перцентили INP, LCP, CLS в Datadog или подобное, алертит когда p75 INP пересекает 200 мс на любом сегменте пользователей (страна, устройство, версия приложения).

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

Профилирование на реальном железе vs DevTools-троттлинг. «4-кратное замедление CPU» в DevTools Performance — приближение, не замена реального железа. M2 MacBook при 4-кратном замедлении всё равно имеет другую модель памяти, другой GPU и другой профиль теплового троттлинга, чем Pixel 6a. Профилируйте на физическом среднеценовом Android хотя бы раз в неделю с помощью Chrome для Android + удалённая отладка, и сравнивайте длительности кадров. Разница >30% означает мобильную регрессию, невидимую на десктопе.

Граничные случаи

Layer squashing — ответ compositor на взрыв слоёв из-за перекрытия. Когда много соседних неанимирующихся элементов продвигаются (из-за перекрытия с единственным анимированным слоем), compositor сжимает их в единый общий «squashed layer» bitmap. Это уменьшает GPU-память ценой большего одиночного bitmap — если один элемент в squash меняется, весь squashed layer должен перерисоваться. Эвристика squashing не управляется пользователем; единственное лечение — изолировать анимированные слои от неанимирующихся соседей, чтобы правило перекрытия не срабатывало.

Спроектируй

Спроектируйте поведение скролла для виртуализированного списка чата, содержащего 50 000 сообщений, с достижением 60 fps на среднеценовом Android-телефоне.

  • Бюджет кадра: 16.67 мс. Реалистичный бюджет главного потока после оверхеда браузера: ~10 мс.
  • Компоновка не должна зависеть от off-screen строк.
  • Composite-only путь во время скролла. Компоновка и отрисовка допустимы только когда новые строки входят в viewport.
  • GPU-память: предположим 200 МБ доступно. Количество слоёв должно оставаться ниже 30 в любой момент.
  • Resize-обработчик не должен зацикливать чтения и записи (без форсированного reflow).
Викторина

Click-обработчик выполняется 80 мс. Пользователь воспринимает задержку перед обновлением UI. Какой Core Web Vital это измеряет и что с этим связано в пайплайне?

Викторина

Touchmove-слушатель подключён без `{passive: true}`. Какая регрессия производительности это вызывает?

Вспомните перед уходом
  1. 01
    Что сообщает LoAF, и чем отличается от INP?
  2. 02
    Почему non-passive scroll/touchmove-слушатели вызывают jank?
  3. 03
    Назовите три уровня CI для перехвата регрессий рендеринга до продакшена.
Итог

LoAF атрибутирует долгие кадры; INP измеряет воспринятую латентность взаимодействия — плохой INP (>200 мс p75) восходит к длинной JS-задаче или форсированной синхронной компоновке в input-обработчике. Non-passive scroll-слушатели откатываются на скролл главного потока; passive: true восстанавливает compositor-скролл. content-visibility: auto display-блокирует off-screen поддеревья, снижая 1200-мс компоновку до 10 мс. @media (prefers-reduced-motion) — одновременно требование доступности и оптимизация бюджета рендеринга. Web Workers разгружают CPU-тяжёлый JS с главного потока; Service Workers обслуживают первую отрисовку из кэша. CI нужны три слоя: Lighthouse на PR, синтетический p95 LoAF на критических путях и RUM-алерты на продовые INP-перцентили. Полная поверхность атаки — блокирующие парсер скрипты, раздутый CSS, сложные селекторы, глубокие flex-компоновки, paint-тяжёлые фильтры, переполнение слоёв, layout thrash, non-passive слушатели, задачи >50 мс и долгие input-обработчики — отображается один к одному на конкретные стадии пайплайна. Теперь, когда пользователь на бюджетном Android в Джакарте сообщает о вялом тапе, у вас есть петля телеметрии: INP даёт взаимодействие, LoAF — кадр, карта стоимости стадий — лечение.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки
встречается в188

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.