{ "index": 19, "slug": "editorial-2027-06-field-performance-capstone", "title": "p95 не изменился: как найти настоящую причину медленной страницы", "excerpt": "Уменьшение bundle не гарантирует быстрый ответ. Разбираем полевой симптом, разделяем сервер, сеть и браузер, а затем принимаем решение по повторяемому p95.", "contentHtml": "

После релиза JavaScript-бандл стал меньше на 180 КБ, но p95 загрузки каталога остался около 3,1 секунды. Пользователь по-прежнему видит пустой первый экран. Цена ошибки — потратить спринт на минификацию, а затем обнаружить, что запрос к базе ждёт 1,8 секунды или браузер тратит время на главный поток. Размер файла изменился. Причина задержки могла остаться прежней.

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

Что именно измеряет p95

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

У любой цифры есть граница. TTFB показывает, когда начал приходить ответ. Он не описывает выполнение JavaScript. LCP показывает момент отрисовки крупнейшего видимого элемента, но зависит от HTML, CSS, шрифта, изображения, viewport и устройства. Размер bundle показывает объём передачи и распаковки, но не говорит, какой запрос блокирует страницу. Эти значения нужно хранить раздельно.

Минимальный протокол одного сравнимого замера
ПолеПримерЗачем оно нужно
Версияcommit abc123Связать результат с конкретным кодом
УсловияChromium, 1280×800, cold cacheНе смешать разные сценарии
Выборка20 повторовПонимать устойчивость p95
Сырые данныеJSON со всеми значениямиПроверить выбросы и пересчитать итог
МетрикиTTFB, LCP, p95, long tasksОтделить сервер от браузера
\"Цикл
Сравнение возвращается к тем же условиям после одного изменения. Иначе разницу нельзя уверенно связать с исправлением.

Механизм: задержка складывается из разных очередей

Навигация начинается с запроса документа. До первого байта браузер ждёт сеть, proxy и сервер. Сервер в этот момент может ждать соединение с базой, блокировку или внешний сервис. После первого байта браузер получает остальной HTML. Затем parser встречает CSS и обычные script. Они меняют порядок загрузки и работы главного потока. Позже браузер выбирает крупный элемент для LCP.

Пусть время до полезного экрана можно представить как сумму server_wait + html_transfer + blocking_resources + main_thread_work + paint. Это не универсальная формула пользовательской метрики. Это рабочая карта расследования. Если TTFB вырос, сначала ищите серверную или сетевую задержку. Если TTFB стабилен, а LCP вырос, смотрите ресурсы, layout и JavaScript. Изменение одной части не подтверждает улучшение всей страницы.

В браузере начните с записи навигации и ресурсов. performance.getEntriesByType('navigation')[0] даёт временные точки документа. Для ресурсов используйте performance.getEntriesByType('resource'). Сопоставьте ранние записи с HTML-тегом или инициатором в waterfall. Не делайте вывод по одной полосе: ресурс мог загрузиться рано, но не влиять на первый экран.

Учебный локальный замер HTTP-пути

Следующий пример специально ограничен локальным HTTP-путём. Он не моделирует браузер, мобильную сеть, CDN или реальную базу данных. Сервер задерживает ответ на 40 миллисекунд. Клиент делает двадцать одинаковых запросов, сохраняет сырые времена и считает медиану и p95. Это позволяет проверить арифметику и увидеть влияние выброса до анализа страницы.

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(); });

В нормальном запуске count равен 20, а значения находятся немного выше 40 миллисекунд из-за накладных расходов процесса. Точное число зависит от машины. Это ожидаемый учебный результат, а не production-результат. Если добавить один искусственный выброс, p95 вырастет, хотя девятнадцать запросов не изменились. Поэтому отчёт хранит и итог, и выборку.

В рабочем замере не смешивайте cold и warm cache. В первом режиме браузер и CDN скачивают ресурсы. Во втором часть данных уже доступна локально. Если перемешать режимы, p95 описывает смесь сценариев. То же относится к viewport, throttling, версии браузера, размеру ответа и состоянию данных.

Симптом → причина → проверка → действие

Карта решения после первого наблюдения
СимптомВозможная причинаПроверкаДействие
TTFB и p95 вырослисервер ждёт базу или upstreamtrace, серверные тайминги, план запросаисправить узкий участок и повторить тот же сценарий
TTFB стабилен, LCP выросблокирующий CSS, шрифт или scriptwaterfall, resource entries, long tasksизменить порядок или размер ресурса, затем проверить первый экран
bundle меньше, LCP тот жеузким местом был не bundleсравнить TTFB, ресурсы и главный потокне объявлять успех; выбрать доминирующий участок
Среднее лучше, p95 хужестал тяжелее хвост или появились выбросысырые значения, размер выборки, нагрузканайти поздние запуски и не заменять p95 средним
Метрика пропаланет поддержки или запись ограничена политикой доступаsupportedEntryTypes, браузер, originпометить отсутствие и выбрать доступный сигнал

Как связать цифру с причиной

Сначала найдите доминирующий участок, а не самое знакомое слово в отчёте. Высокий TTFB не доказывает, что виновата база. Он только говорит, что ответ начал приходить поздно. Разделите server timing, сеть и proxy. Если серверная часть стабильна, проверьте передачу HTML и очередь ресурсов.

Ранний script тоже не равен проблеме. Он может быть маленьким и нужным для маршрутизации. Большой script может прийти поздно и не влиять на LCP, если крупный элемент уже отрисован. Смотрите на блокировку главного потока и на связь с конкретным элементом. В отрицательном пути команда не находит причины, потому что проверяет только размер файла. Тогда замер нужно остановить и расширить до навигации, ресурсов и trace.

Изменяйте один фактор за раз. Например, сначала уберите лишний preload, затем повторите двадцать запусков с теми же условиями. Не меняйте одновременно SQL, компрессию, порядок script и viewport. Иначе улучшение или регрессия не принадлежит одному решению.

Порядок действий

  1. Запишите симптом, URL, commit, браузер, viewport, сеть и режим кэша.
  2. Выберите одну метрику решения и сохраните связанные метрики: TTFB, LCP, p95 и long tasks.
  3. Сделайте одинаковую серию запусков и сохраните каждое сырое значение.
  4. Разделите задержку на сервер, HTML, ресурсы, главный поток и отрисовку.
  5. Проверьте одну гипотезу минимальным изменением, которое можно откатить.
  6. Повторите серию при тех же условиях и сравните распределения, а не только средние.
  7. Проверьте отрицательный путь: cold cache, слабый CPU, поздний upstream или отсутствующую запись.
  8. Зафиксируйте действие, ограничение вывода и результат для того же критерия.

Ограничения и критерий готовности

Локальный стенд проверяет код подсчёта, но не пользовательскую скорость. Лабораторный browser-run показывает повторяемость, но не покрывает все устройства и сети. Полевой p95 зависит от состава пользователей, частоты запусков и способа агрегации. Порог нельзя переносить между страницами без объяснения.

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

Проверяемые источники

" }