8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 19,
|
||
"slug": "editorial-2027-06-field-performance-capstone",
|
||
"title": "p95 не изменился: как найти настоящую причину медленной страницы",
|
||
"excerpt": "Уменьшение bundle не гарантирует быстрый ответ. Разбираем полевой симптом, разделяем сервер, сеть и браузер, а затем принимаем решение по повторяемому p95.",
|
||
"contentHtml": "<p>После релиза JavaScript-бандл стал меньше на 180 КБ, но p95 загрузки каталога остался около 3,1 секунды. Пользователь по-прежнему видит пустой первый экран. Цена ошибки — потратить спринт на минификацию, а затем обнаружить, что запрос к базе ждёт 1,8 секунды или браузер тратит время на главный поток. Размер файла изменился. Причина задержки могла остаться прежней.</p><p>Такой симптом нельзя лечить одним советом вроде «включите кеш» или «сократите JavaScript». Сначала разложите задержку по участкам одного запуска: ожидание ответа, передача HTML и ресурсов, выполнение кода, отрисовка крупного элемента. Тезис статьи прост: результат замера становится инженерным доказательством только тогда, когда команда сохраняет условия, сырые наблюдения и метрику, которой принято решение.</p><h2>Что именно измеряет p95</h2><p>p95 — это значение, ниже которого лежат 95 процентов наблюдений в выбранной выборке. Пять процентов измерений находятся выше него. Это характеристика хвоста, а не обещание для каждого пользователя. На двадцати замерах один поздний запрос уже заметно влияет на p95. На пяти замерах такая оценка почти неустойчива.</p><p>У любой цифры есть граница. TTFB показывает, когда начал приходить ответ. Он не описывает выполнение JavaScript. LCP показывает момент отрисовки крупнейшего видимого элемента, но зависит от HTML, CSS, шрифта, изображения, viewport и устройства. Размер bundle показывает объём передачи и распаковки, но не говорит, какой запрос блокирует страницу. Эти значения нужно хранить раздельно.</p><table><caption>Минимальный протокол одного сравнимого замера</caption><thead><tr><th scope=\"col\">Поле</th><th scope=\"col\">Пример</th><th scope=\"col\">Зачем оно нужно</th></tr></thead><tbody><tr><td>Версия</td><td>commit abc123</td><td>Связать результат с конкретным кодом</td></tr><tr><td>Условия</td><td>Chromium, 1280×800, cold cache</td><td>Не смешать разные сценарии</td></tr><tr><td>Выборка</td><td>20 повторов</td><td>Понимать устойчивость p95</td></tr><tr><td>Сырые данные</td><td>JSON со всеми значениями</td><td>Проверить выбросы и пересчитать итог</td></tr><tr><td>Метрики</td><td>TTFB, LCP, p95, long tasks</td><td>Отделить сервер от браузера</td></tr></tbody></table><figure><img src=\"/assets/editorial/2027/performance-capstone-2027-handoff-loop.svg\" alt=\"Цикл измерения производительности: условия, серия запусков, распределение, одно изменение и повторная проверка\" loading=\"lazy\" /><figcaption>Сравнение возвращается к тем же условиям после одного изменения. Иначе разницу нельзя уверенно связать с исправлением.</figcaption></figure><h2>Механизм: задержка складывается из разных очередей</h2><p>Навигация начинается с запроса документа. До первого байта браузер ждёт сеть, proxy и сервер. Сервер в этот момент может ждать соединение с базой, блокировку или внешний сервис. После первого байта браузер получает остальной HTML. Затем parser встречает CSS и обычные script. Они меняют порядок загрузки и работы главного потока. Позже браузер выбирает крупный элемент для LCP.</p><p>Пусть время до полезного экрана можно представить как сумму <code>server_wait + html_transfer + blocking_resources + main_thread_work + paint</code>. Это не универсальная формула пользовательской метрики. Это рабочая карта расследования. Если TTFB вырос, сначала ищите серверную или сетевую задержку. Если TTFB стабилен, а LCP вырос, смотрите ресурсы, layout и JavaScript. Изменение одной части не подтверждает улучшение всей страницы.</p><p>В браузере начните с записи навигации и ресурсов. <code>performance.getEntriesByType('navigation')[0]</code> даёт временные точки документа. Для ресурсов используйте <code>performance.getEntriesByType('resource')</code>. Сопоставьте ранние записи с HTML-тегом или инициатором в waterfall. Не делайте вывод по одной полосе: ресурс мог загрузиться рано, но не влиять на первый экран.</p><h2>Учебный локальный замер HTTP-пути</h2><p>Следующий пример специально ограничен локальным HTTP-путём. Он не моделирует браузер, мобильную сеть, CDN или реальную базу данных. Сервер задерживает ответ на 40 миллисекунд. Клиент делает двадцать одинаковых запросов, сохраняет сырые времена и считает медиану и p95. Это позволяет проверить арифметику и увидеть влияние выброса до анализа страницы.</p><pre><code>import { performance } from 'node:perf_hooks'; import { createServer } from 'node:http'; const server = createServer((request, response) => { setTimeout(() => response.end('ready'), 40); }); function percentile(values, rank) { const sorted = [...values].sort((a, b) => a - b); const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * rank) - 1); return sorted[index]; } server.listen({ host: '127.0.0.1', port: 0 }, async () => { const { port } = server.address(); const samples = []; for (let attempt = 0; attempt < 20; attempt += 1) { const started = performance.now(); await (await fetch('http://127.0.0.1:' + port)).text(); samples.push(performance.now() - started); } console.log({ count: samples.length, median: percentile(samples, 0.5).toFixed(1), p95: percentile(samples, 0.95).toFixed(1), samples: samples.map(value => value.toFixed(1)) }); server.close(); });</code></pre><p>В нормальном запуске <code>count</code> равен 20, а значения находятся немного выше 40 миллисекунд из-за накладных расходов процесса. Точное число зависит от машины. Это ожидаемый учебный результат, а не production-результат. Если добавить один искусственный выброс, p95 вырастет, хотя девятнадцать запросов не изменились. Поэтому отчёт хранит и итог, и выборку.</p><p>В рабочем замере не смешивайте cold и warm cache. В первом режиме браузер и CDN скачивают ресурсы. Во втором часть данных уже доступна локально. Если перемешать режимы, p95 описывает смесь сценариев. То же относится к viewport, throttling, версии браузера, размеру ответа и состоянию данных.</p><h2>Симптом → причина → проверка → действие</h2><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>TTFB и p95 выросли</td><td>сервер ждёт базу или upstream</td><td>trace, серверные тайминги, план запроса</td><td>исправить узкий участок и повторить тот же сценарий</td></tr><tr><td>TTFB стабилен, LCP вырос</td><td>блокирующий CSS, шрифт или script</td><td>waterfall, resource entries, long tasks</td><td>изменить порядок или размер ресурса, затем проверить первый экран</td></tr><tr><td>bundle меньше, LCP тот же</td><td>узким местом был не bundle</td><td>сравнить TTFB, ресурсы и главный поток</td><td>не объявлять успех; выбрать доминирующий участок</td></tr><tr><td>Среднее лучше, p95 хуже</td><td>стал тяжелее хвост или появились выбросы</td><td>сырые значения, размер выборки, нагрузка</td><td>найти поздние запуски и не заменять p95 средним</td></tr><tr><td>Метрика пропала</td><td>нет поддержки или запись ограничена политикой доступа</td><td>supportedEntryTypes, браузер, origin</td><td>пометить отсутствие и выбрать доступный сигнал</td></tr></tbody></table><h2>Как связать цифру с причиной</h2><p>Сначала найдите доминирующий участок, а не самое знакомое слово в отчёте. Высокий TTFB не доказывает, что виновата база. Он только говорит, что ответ начал приходить поздно. Разделите server timing, сеть и proxy. Если серверная часть стабильна, проверьте передачу HTML и очередь ресурсов.</p><p>Ранний script тоже не равен проблеме. Он может быть маленьким и нужным для маршрутизации. Большой script может прийти поздно и не влиять на LCP, если крупный элемент уже отрисован. Смотрите на блокировку главного потока и на связь с конкретным элементом. В отрицательном пути команда не находит причины, потому что проверяет только размер файла. Тогда замер нужно остановить и расширить до навигации, ресурсов и trace.</p><p>Изменяйте один фактор за раз. Например, сначала уберите лишний preload, затем повторите двадцать запусков с теми же условиями. Не меняйте одновременно SQL, компрессию, порядок script и viewport. Иначе улучшение или регрессия не принадлежит одному решению.</p><h2>Порядок действий</h2><ol><li>Запишите симптом, URL, commit, браузер, viewport, сеть и режим кэша.</li><li>Выберите одну метрику решения и сохраните связанные метрики: TTFB, LCP, p95 и long tasks.</li><li>Сделайте одинаковую серию запусков и сохраните каждое сырое значение.</li><li>Разделите задержку на сервер, HTML, ресурсы, главный поток и отрисовку.</li><li>Проверьте одну гипотезу минимальным изменением, которое можно откатить.</li><li>Повторите серию при тех же условиях и сравните распределения, а не только средние.</li><li>Проверьте отрицательный путь: cold cache, слабый CPU, поздний upstream или отсутствующую запись.</li><li>Зафиксируйте действие, ограничение вывода и результат для того же критерия.</li></ol><h2>Ограничения и критерий готовности</h2><p>Локальный стенд проверяет код подсчёта, но не пользовательскую скорость. Лабораторный browser-run показывает повторяемость, но не покрывает все устройства и сети. Полевой p95 зависит от состава пользователей, частоты запусков и способа агрегации. Порог нельзя переносить между страницами без объяснения.</p><p>Работа готова, когда другая команда может открыть отчёт, увидеть исходные условия и пересчитать p95 из сохранённых значений. В отчёте есть один доминирующий участок, проверенная гипотеза, повтор после одного изменения и отрицательный сценарий. Для страницы критерий должен включать конкретную метрику и порог, например: p95 LCP не выше согласованного значения при указанном браузере, viewport, сети и режиме кэша. Это проверяемое утверждение. «Стало быстрее» — нет.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.w3.org/TR/navigation-timing-2/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Navigation Timing Level 2</a> — интерфейс временных точек навигации документа. Спецификация является рабочим черновиком и не задаёт порог качества для конкретного сайта.</li><li><a href=\"https://www.w3.org/TR/resource-timing/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Resource Timing</a> — записи загрузки ресурсов и их временные границы. Доступность и детализация зависят от браузера и политики origin.</li><li><a href=\"https://www.w3.org/TR/performance-timeline/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Performance Timeline</a> — общий интерфейс чтения PerformanceEntry и наблюдения за записями. Он не связывает автоматически метрику с причиной задержки.</li></ul>"
|
||
}
|