8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 59,
|
||
"slug": "editorial-2026-05-mechanism-systems-performance",
|
||
"title": "Почему короткий trace не доказывает ускорение системы",
|
||
"excerpt": "Как отделить ожидание от работы, проверить сопоставимость нагрузки и остановить разбор, когда trace не подтверждает причинный вывод.",
|
||
"contentHtml": "<p>Фраза «БД медленная» часто появляется после одного взгляда на trace. На ней виден длинный промежуток, но не видно, ждёт ли запрос очередь, выполняет ли БД работу или задерживается внешний вызов. Цена ошибки — недели оптимизации не того участка. Команда меняет SQL, а пользовательский путь не становится короче.</p>\n<p>Надёжный разбор начинается с более узкого тезиса: длительность показывает интервал, но не его причину. Чтобы сравнить два результата, нужно одновременно сохранить связную trace, одинаковую границу нагрузки и ограниченный вывод. Если одно условие нарушено, правильное действие — остановиться и назвать недостающий факт.</p>\n<h2>Одна trace и три разных времени</h2>\n<p>Рассмотрим учебный пример. Root span описывает путь запроса от входа до ответа. Внутри него идут три последовательных интервала: admission queue ждёт 520 условных единиц, вызов БД занимает 150, внешний каталог — 200. Эти единицы придуманы для примера. Они не являются миллисекундами, метрикой сервиса или результатом production-измерения.</p>\n<table><caption>Что видно в учебной trace и что можно сказать</caption><thead><tr><th scope=\"col\">Сегмент</th><th scope=\"col\">Роль</th><th scope=\"col\">Интервал</th><th scope=\"col\">Допустимый вывод</th></tr></thead><tbody><tr><td>fixed-admission-queue</td><td>queue-wait</td><td>40–560, 520 units</td><td>Самый длинный названный сегмент этого input</td></tr><tr><td>fixed-db-call</td><td>database-execution</td><td>570–720, 150 units</td><td>Интервал вызова БД в этой trace</td></tr><tr><td>fixed-catalog-call</td><td>external-dependency</td><td>730–930, 200 units</td><td>Интервал внешнего вызова в этой trace</td></tr><tr><td>fixed-gateway</td><td>end-to-end</td><td>0–1000, 1000 units</td><td>Граница пути, но не объяснение причины</td></tr></tbody></table>\n<p>Таблица не говорит, что очередь замедляет production. Она не говорит, что SQL нужно переписать. Она фиксирует структуру одного учебного input. Это важное различие. Длинный span можно ранжировать. Причину нужно проверять отдельным экспериментом с той же границей.</p>\n<figure><img src=\"/assets/editorial/2026/systems-performance-2026-load-latency-curve.svg\" alt=\"Две учебные точки нагрузки и задержки: короткий интервал при другой нагрузке не образует сопоставимое сравнение\" loading=\"lazy\" /><figcaption>Рисунок. Более короткий root не доказывает улучшение, если cohort, concurrency или форма входа изменились.</figcaption></figure>\n<h2>Почему root span легко вводит в заблуждение</h2>\n<p>Допустим, второй root равен 800 вместо 1000. На графике он выглядит лучше. Но во втором input одновременно изменились cohort, число логических запросов с 12 до 24, concurrency с 3 до 6 и форма чтения. Такой результат нельзя приписать предполагаемой оптимизации. Он описывает другой сценарий.</p>\n<p>Контрольная граница должна быть записана до сравнения длительностей. Для этого учебного примера она состоит из четырёх полей: <code>cohort=fixed-load-a</code>, <code>logicalRequests=12</code>, <code>concurrency=3</code> и <code>inputShape=fixed-read-shape-a</code>. Это не универсальный стандарт нагрузки. Это минимальный контракт конкретной проверки. В другом сценарии набор полей будет иным, но правило останется тем же: заранее назвать условия, которые должны совпасть.</p>\n<pre><code>const boundary = {\\n cohort: 'fixed-load-a',\\n logicalRequests: 12,\\n concurrency: 3,\\n inputShape: 'fixed-read-shape-a',\\n};\\n\\n// Учебный пример: отсутствие совпадающей границы\\n// запрещает называть разницу эффектом изменения.\\nconst comparable = sameBoundary(baseline, candidate, boundary);</code></pre>\n<p>Код выше иллюстрирует только порядок проверки. Функция <code>sameBoundary</code> не измеряет latency и не находит bottleneck. Она отвечает на более простой вопрос: можно ли поставить два учебных результата рядом. В реальном проекте нужно явно определить сравниваемые поля, единицы измерения и правила обработки пропусков.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностическая таблица для разбора trace</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Возможная причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Длинный интервал перед работой</td><td>Ожидание в admission queue</td><td>Есть отдельный span с ролью queue-wait и parent внутри root</td><td>Оставить наблюдение; не называть очередь причиной production-задержки</td></tr><tr><td>Длинный span помечен только internal</td><td>Класс задержки неизвестен</td><td>Проверить роль и границы интервала</td><td>Вернуть stop-hidden-queue и назвать ожидание unknown</td></tr><tr><td>Root стал короче</td><td>Изменилась нагрузка или форма входа</td><td>Сверить cohort, requests, concurrency и input shape</td><td>При расхождении вернуть stop-incomparable-load</td></tr><tr><td>Дочерний span ссылается на отсутствующий parent</td><td>Trace неполна</td><td>Проверить связность дерева и интервалы</td><td>Вернуть stop-incomplete-trace; не восстанавливать связь догадкой</td></tr><tr><td>В записи есть «стало быстрее»</td><td>Заявлен эффект без контроля</td><td>Найти baseline с той же границей и явный критерий</td><td>Снять effect claim и оставить только наблюдение</td></tr></tbody></table>\n<h2>Fail-closed: отрицательный путь важнее красивого графика</h2>\n<p>Неполный материал должен завершать разбор отказом. У учебного input <code>incomplete-trace-v1</code> дочерний span указывает на отсутствующий parent. Нельзя определить, относится ли интервал к этому пути. Статус <code>stop-incomplete-trace</code> точнее, чем попытка соединить span по времени или имени.</p>\n<p>У <code>hidden-queue-v1</code> длинный интервал называется <code>fixed-unclassified-delay</code>. Он похож на ожидание, но такого сходства недостаточно. Статус <code>stop-hidden-queue</code> означает: сначала назовите границу ожидания, затем продолжайте. Иначе команда переложит время на очередь только потому, что она удобна как объяснение.</p>\n<p>У <code>incomparable-load-v1</code> root равен 800, но нагрузка отличается от baseline. Статус <code>stop-incomparable-load</code> не утверждает, что число 800 неверно. Он запрещает делать из него вывод об ускорении. Наконец, <code>unsupported-effect-v1</code> содержит фразу <code>faster-after-change</code> без допустимого контрольного сравнения. Для него нужен <code>stop-unsupported-effect</code>.</p>\n<pre><code>for (const id of [\\n 'hidden-queue-v1',\\n 'incomparable-load-v1',\\n 'unsupported-effect-v1',\\n]) {\\n const result = reviewFixedPerformanceInput(createInput(id));\\n console.log(id, result.status, result.nextAction);\\n}\\n\\n// Учебный результат: stop — нормальный исход проверки.\\n// Он не доказывает, что система медленная.</code></pre>\n<p>Отказ должен быть машинно и текстово видимым. Он сохраняет конкретную причину остановки и следующее действие. Общая фраза «нужно больше данных» хуже: она не показывает, какого именно условия не хватает.</p>\n<h2>Длительность не равна причинности</h2>\n<p>Даже связная trace не сообщает автоматически, почему очередь заняла 520 units. Причиной может быть лимит ресурса, политика admission, форма учебного сценария или другая часть системы. Роль span <code>CLIENT</code>, <code>SERVER</code> или <code>INTERNAL</code> помогает описать операцию. Она не превращает интервал в диагноз и не назначает оптимизацию.</p>\n<p>Правильная цепочка выглядит так: наблюдение — <code>queue-wait=520</code>; гипотеза — правило admission создаёт ожидание; новая проверка — заранее определённый input с той же границей и одним изменённым условием; результат — сравнимое наблюдение или новый stop. Перескок от первого пункта к утверждению «очередь является корнем проблемы» нарушает границу доказательства.</p>\n<p>То же относится к БД. <code>database-execution=150</code> не является SQL-профилем, индексом, бюджетом или обещанием. Это интервал вызова в учебной trace. Если нужен разбор SQL, он требует отдельного measurement contract, собственных входов и критерия сравнения.</p>\n<h2>Порядок действий</h2>\n<ol><li>Выбрать одну trace и один root span. Не собирать критический путь из разрозненных логов и таймеров.</li><li>Проверить дерево: у каждого дочернего span есть существующий parent, начало не позже конца, root покрывает выбранный путь.</li><li>Разделить ожидание, исполнение БД и внешний вызов. Если роль неизвестна, остановить разбор.</li><li>Записать контрольную границу до просмотра того, какой root короче: cohort, число запросов, concurrency и форма входа.</li><li>Сравнить только совпадающие inputs. Различие хотя бы одного обязательного поля — отдельное наблюдение, а не эффект изменения.</li><li>Сформулировать вывод ровно по данным: например, «queue-wait — самый длинный названный сегмент этого учебного input».</li><li>Проверить отрицательный путь и сохранить status. Если вход не проходит проверку, передать stop reason, а не рекомендацию по оптимизации.</li></ol>\n<h2>Ограничения</h2>\n<p>Учебная trace не даёт распределения latency, хвостов, throughput, variance, queue discipline или стоимости ресурсов. Условные units нельзя переводить в миллисекунды. Один root не заменяет серию измерений. Связь через trace-id не гарантирует полноту дерева. Пересекающиеся span нельзя бездумно складывать: они могут выполняться параллельно.</p>\n<p>Источники ниже описывают контекст trace, роли span и семантику HTTP. Они не доказывают bottleneck, не обещают latency и не подтверждают production-эффект. Поэтому материал ограничивает вывод учебным input и не предлагает rollout, изменение конфигурации или выбор индекса.</p>\n<h2>Критерий готовности</h2>\n<p>Разбор готов, если другой инженер получает тот же status на том же именованном input, видит связное дерево, понимает контрольную границу и может указать, какое условие приводит к stop. В принятом учебном случае вывод должен остаться наблюдением: queue wait — самый длинный названный сегмент в данной trace; причинность и эффект изменения не заявлены. Если для чтения вывода нужны устные пояснения, внешний дашборд или догадка автора, материал не готов.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://www.w3.org/TR/trace-context/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Trace Context</a> — формат связи trace и parent; источник не доказывает полноту наблюдений или производительность.</li><li><a href=\"https://opentelemetry.io/docs/specs/otel/trace/api/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Trace API</a> — описательные роли и API trace; роль span не является причинным диагнозом.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9110: HTTP Semantics</a> — семантика HTTP; документ не задаёт SLA по latency или capacity.</li></ul>"
|
||
}
|