Files
progcode/editorial/agent-rewrites/019.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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) =&gt; { setTimeout(() =&gt; response.end('ready'), 40); }); function percentile(values, rank) { const sorted = [...values].sort((a, b) =&gt; 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 () =&gt; { const { port } = server.address(); const samples = []; for (let attempt = 0; attempt &lt; 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 =&gt; 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>"
}