8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 211,
|
||
"slug": "editorial-2022-02-field-performance-budget",
|
||
"title": "Когда total зелёный, а компонент красный: как проверять бюджет производительности",
|
||
"excerpt": "Общий score может пройти, пока отдельная часть маршрута уже превысила допуск. Разбираем контракт сравнения, named budgets, отрицательный путь и проверяемое действие без выдуманных production-выводов.",
|
||
"contentHtml": "<p>Страница не стала заметно медленнее по общему числу, но один этап маршрута пересёк свой предел. CI показывает зелёный <code>total</code>, а отчёт рядом отмечает красный <code>scriptTicks</code>. Команда пропускает изменение, потому что итог выглядит безопасным. Цена ошибки — накопленная деградация: следующий релиз добавит ещё один небольшой расход, а найти момент поломки будет уже трудно.</p>\n<p>Бюджет производительности нужен не для одного красивого числа. Он разделяет маршрут на именованные части и проверяет каждую часть в одинаковых условиях. Если <code>total</code> равен 93 при допустимых 98, а <code>scriptTicks</code> равен 34 при допустимых 30, результат должен быть отказом компонента. Зелёная сумма не отменяет красную ветку. Это учебный пример с условными единицами. Он не сообщает скорость реальной страницы.</p>\n<h2>Тезис: сначала контракт, потом цифры</h2>\n<p>Сравнение baseline и candidate имеет смысл только при одном контракте. Контракт включает маршрут, сценарий, версию рабочей нагрузки, единицу измерения и условия запуска. К условиям относятся, например, состояние кеша, набор данных и профиль устройства. Если хотя бы одно условие изменилось, различие чисел нельзя назвать регрессией. Сначала нужно вернуть сопоставимость.</p>\n<p>После проверки контракта система проверяет named components. Для каждого компонента нужны четыре значения: фактическое значение, limit, tolerance и итоговый allowed. Формула проста: <code>allowed = limit + tolerance</code>. Компонент проходит, если <code>actual <= allowed</code>. Aggregate вычисляется отдельно. Он показывает запас общего бюджета, но не получает права скрывать отказ части.</p>\n<p>Такой порядок ограничивает вывод. Comparable PASS означает только то, что известные проверки прошли. Comparable FAIL означает, что при одинаковых условиях хотя бы один именованный компонент превысил предел. Несопоставимый вход не означает FAIL и не означает PASS. Он означает, что сравнение остановилось до интерпретации.</p>\n<h2>Механизм на коротком примере</h2>\n<p>Представим маршрут <code>/training/checkout/review</code> и сценарий анонимной корзины с одним товаром. В бюджете заданы четыре учебных компонента. В baseline скрипты занимают 26 условных тиков. В candidate — 34. Limit равен 28, tolerance — 2, поэтому allowed равен 30. Aggregate candidate равен 93 при aggregate allowed 98.</p>\n<pre><code>const budget = {\n components: {\n documentTicks: { limit: 26, tolerance: 1 },\n styleTicks: { limit: 16, tolerance: 1 },\n scriptTicks: { limit: 28, tolerance: 2 },\n renderTicks: { limit: 22, tolerance: 1 }\n },\n aggregateAllowed: 98\n};\n\nconst candidate = {\n route: \"/training/checkout/review\",\n scenario: \"anonymous-cart-with-one-item\",\n conditions: { cache: \"warm\", workloadVersion: 1 },\n timingFields: {\n documentTicks: 24,\n styleTicks: 15,\n scriptTicks: 34,\n renderTicks: 20\n }\n};\n\nconst scriptAllowed =\n budget.components.scriptTicks.limit +\n budget.components.scriptTicks.tolerance;\n\nconsole.log(candidate.timingFields.scriptTicks <= scriptAllowed);\n// false: 34 > 30\nconsole.log(93 <= budget.aggregateAllowed);\n// true: aggregate не скрывает failed component</code></pre>\n<p>В реальном коде проверка должна вернуть не только boolean. Отчёту нужны имя компонента, actual, allowed, status и граница следующего действия. Иначе человеку придётся восстановить причину по общей сумме. Это снова превращает бюджет в декоративный показатель.</p>\n<p>Следующий шаг после такого отказа не обязан быть большим. Сначала проверьте вход, который принадлежит <code>scriptTicks</code>: размер изменившегося bundle, новый dynamic import, число обработанных элементов или другой заранее выбранный источник. Если источник не определён, не называйте библиотеку причиной. Число 34 показывает нарушение допуска, но не объясняет его.</p>\n<h2>Как читать отчёт</h2>\n<p>Читайте результат сверху вниз. Сначала откройте validation и убедитесь, что baseline и candidate сравнимы. Затем посмотрите список failed components. После этого прочитайте actual и allowed конкретной ветки. Aggregate оставьте напоследок. Такой порядок не даёт зелёному total занять место решения.</p>\n<table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Total PASS, component FAIL</td><td>Сумма скрывает распределение расходов</td><td>Сравнить actual с allowed по имени</td><td>Считать overall FAIL и исследовать одну границу</td></tr><tr><td>Входы отличаются</td><td>Baseline и candidate несопоставимы</td><td>Сверить route, scenario и conditions</td><td>Остановить verdict и повторить с одним контрактом</td></tr><tr><td>Все components PASS, total растёт</td><td>Общий запас сокращается</td><td>Сравнить aggregate с его пределом</td><td>Найти компонент с наибольшим вкладом</td></tr><tr><td>Один компонент скачет между запусками</td><td>Шум измерения или нестабильное условие</td><td>Проверить повторяемость и профиль запуска</td><td>Уточнить протокол до изменения limit</td></tr><tr><td>Причина не видна в отчёте</td><td>Бюджет хранит только число</td><td>Проверить наличие source и owner boundary</td><td>Добавить один наблюдаемый вход, а не гипотезу</td></tr></tbody></table>\n<h2>Иллюстрация границ вывода</h2>\n<figure><img src=\"/assets/editorial/2022/performance-budget-diagnosis-2022.svg\" alt=\"Схема сравнения бюджета: сначала проверяется контракт, затем именованные компоненты и только потом общий total; красный scriptTicks ведёт к узкой проверке даже при зелёной сумме\" loading=\"lazy\" /><figcaption>Сначала проверяется сопоставимость входов, затем named component и aggregate. Учебная схема показывает порядок решения, а не измерение конкретного production-маршрута.</figcaption></figure>\n<p>Иллюстрация важна из-за отрицательного пути. Если cache или scenario изменились, стрелка не должна вести к красной метрике. Система должна вернуть состояние «сравнение недействительно» и объяснить, какое условие разошлось. Если этого не сделать, изменение среды выглядит как изменение приложения.</p>\n<h2>Порядок действий</h2>\n<ol><li><strong>Запишите симптом.</strong> Сохраните route, scenario, baseline, candidate и точное имя компонента. Не начинайте с предположения о виновной библиотеке.</li><li><strong>Проверьте контракт.</strong> Сверьте версию рабочей нагрузки, единицы, кеш, данные, профиль запуска и остальные объявленные условия. Любое различие блокирует числовой verdict.</li><li><strong>Проверьте allocation.</strong> Для каждого компонента посчитайте <code>allowed = limit + tolerance</code>. Укажите actual и границу рядом.</li><li><strong>Отделите component от aggregate.</strong> Сначала сформируйте список failed components. Общую сумму используйте как контекст, не как разрешение пропустить красную ветку.</li><li><strong>Сузьте исследование.</strong> Выберите одну границу: bundle, route input, dynamic import, обработку данных или другую реально наблюдаемую часть. Следующий сбор должен различать хотя бы две гипотезы.</li><li><strong>Повторите тот же compare.</strong> После небольшого изменения сохраните прежний контракт. Если маршрут, сценарий или условия изменились, создайте новый baseline и укажите причину, а не сравнивайте несопоставимые числа.</li><li><strong>Зафиксируйте предел вывода.</strong> Напишите, что результат подтверждает и чего не подтверждает. Учебные ticks не превращайте в browser trace, пользовательскую метрику или SLA.</li></ol>\n<h2>Как связать учебные поля с браузером</h2>\n<p>Платформа даёт реальные источники, но не готовую таблицу для любого проекта. <code>PerformanceNavigationTiming</code> описывает временные отметки навигации текущего документа. User Timing даёт named marks и measures. Эти интерфейсы помогают выбрать источник для конкретного production-поля. Они не говорят, что условный <code>scriptTicks</code> равен времени выполнения JavaScript или что четыре учебных тика уже собраны браузером.</p>\n<p>Перед переносом модели составьте mapping для каждого поля: имя бюджета, источник, момент получения, единица, условия доступности, преобразование и владелец. Если поле зависит от браузера, укажите поддержку и fallback. Если данных нет, верните отсутствие данных. Не подставляйте похожее число из другого API только потому, что оно удобно для формулы.</p>\n<p>Например, navigation entry может описать загрузку документа, но не объяснить стоимость долгого обработчика после загрузки. Для пользовательского взаимодействия нужен отдельный источник и отдельная методика. Смешивание этих наблюдений в один <code>total</code> создаёт точный, но бессмысленный score. Named budget полезен только там, где его граница совпадает с тем, что действительно измеряется.</p>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Бюджет не доказывает, что страница быстрая для всех пользователей. Один запуск не заменяет распределение по устройствам, сетям и сценариям. Aggregate не заменяет пользовательские метрики. Synthetic compare не является браузерным trace, если браузер не выполнял заданную процедуру. Учебные числа в примере нельзя выдавать за наблюдения реального сервиса.</p>\n<p>Есть и обратный путь после отказа. Если повторная проверка показывает, что изменился cache, а не код, не повышайте limit и не объявляйте regression. Исправьте условия и повторите сравнение. Если условия одинаковы, но компонент снова превышает allowed, собирайте один конкретный источник на его границе. Если источник не позволяет отличить причины, это ограничение знания, а не повод написать более сильный вывод.</p>\n<p>Повышение limit допустимо только как отдельное решение. Оно меняет защиту от роста и должно иметь владельца, причину и новый ожидаемый предел. Нельзя лечить failed component увеличением aggregate: это убирает сигнал, но не уменьшает стоимость маршрута.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Проверка готова, когда система выполняет четыре условия. Она останавливает compare при различии контракта. Она показывает каждый component actual, limit, tolerance и allowed. Она возвращает FAIL, если хотя бы один named component превысил allowed, даже при зелёном aggregate. Она выводит следующее узкое действие и явно отделяет измеренное от неизвестного.</p>\n<p>Для учебного примера критерий можно проверить так: одинаковые route, scenario и conditions дают <code>scriptTicks=34</code>, <code>allowed=30</code>, <code>aggregate=93</code>, <code>aggregateAllowed=98</code> и общий статус FAIL; изменение только cache или scenario даёт состояние несопоставимости; ни один из этих результатов не называется production-измерением. Для реального маршрута к этому набору добавьте источник каждого поля и повторяемый протокол запуска.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.w3.org/TR/navigation-timing-2/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Navigation Timing Level 2</a> — спецификация интерфейса <code>PerformanceNavigationTiming</code> и временных отметок навигации.</li><li><a href=\"https://www.w3.org/TR/user-timing-3/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C User Timing Level 3</a> — спецификация именованных marks и measures.</li><li><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming\" target=\"_blank\" rel=\"noopener noreferrer\">MDN: PerformanceNavigationTiming</a> — актуальное практическое описание интерфейса, его свойств и ограничений.</li></ul>"
|
||
}
|