Проблема начинается с фразы «БД медленная», когда trace показывает только длинный промежуток. Цена этой подмены — оптимизация исполнения при реальном ожидании в очереди либо при внешнем вызове. Механика performance review начинается не с percentiles и не с profiler: она требует разделить ожидание, работу и зависимость в одной сопоставимой synthetic trace.
Здесь три независимые оси: роль сегмента, нагрузочная граница и вывод. Роль отвечает, что именно названо в литерале; нагрузка — можно ли поставить рядом baseline; вывод — какое утверждение допускает доказательство. Если оси смешать, короткий root span при иной concurrency легко выдают за ускорение.
Три вида времени, которые нельзя складывать в одну причину
Queue wait — время до начала работы в названной admission queue. Database execution — интервал вызова БД в synthetic trace. External dependency — интервал исходящего вызова каталога. Их длительности можно расположить рядом; причинный ярлык переносить нельзя. CLIENT и SERVER из OpenTelemetry — описательные kinds, а не автоматическая классификация bottleneck.
| Наблюдение в fixed literal | Чего этого недостаточно доказать | Разрешённая формулировка | Стоп-причина |
|---|---|---|---|
| queue-wait 520 units | что очередь замедляет production | ожидание — длиннейший названный сегмент данного input | нет иной нагрузки |
| db-call 150 units | что SQL надо оптимизировать | исполнение БД занимает 150 units в этой записи | нет причины |
| root 800 вместо 1 000 | что изменение ускорило путь | другой input имеет другой интервал | different cohort/load |
| unclassified-delay 520 | что это очередь | задержка не классифицирована | hidden queue |
Проверка сопоставимости
Для допуска к сравнению литерал хранит cohort fixed-load-a, 12 logical requests, concurrency 3 и fixed-read-shape-a. Это не универсальный набор метрик. Это минимальный контракт конкретного exercise. Смена хотя бы одного поля переводит результат в stop-incomparable-load, потому что разница может принадлежать нагрузке, а не предполагаемому изменению.
Fail-closed вместо правдоподобной догадки
У hidden-queue-v1 интервал 40–560 специально помечен unknown-delay. Он выглядит как очередь, но функция останавливается: stop-hidden-queue. У incomplete-trace-v1 дочерний span ссылается на отсутствующего parent — функция возвращает stop-incomplete-trace. Так review фиксирует недостающий факт, а не превращает форму диаграммы в диагноз.
import { createFixedPerformanceReviewInput, reviewFixedPerformanceInput } from './upgrade-2026-05.mjs';
for (const id of ['hidden-queue-v1', 'incomparable-load-v1']) {
const result = reviewFixedPerformanceInput(createFixedPerformanceReviewInput(id));
console.log({ id, accepted: result.accepted, reason: result.reasons[0] });
}Длительность — не причинность
Длинный span говорит, что интервал записан длинным в конкретном литерале. Он не говорит, почему интервал получился длинным и какое изменение его сократит. Очередь может быть следствием выбранной дисциплины, ограниченного ресурса или искусственно заданного сценария; БД может ждать сеть; внешний вызов может включать локальную подготовку. Даже в одинаковой trace роль и причинный механизм — разные утверждения. Функция намеренно не принимает поле «cause», потому что fixture его не доказывает.
Полезный разрыв выглядит так: наблюдение — queue-wait = 520; гипотеза — «admission rule создаёт ожидание»; проверка гипотезы — другой заранее определённый synthetic literal с тем же контролем; эффект — только сравнение разрешённых результатов. Нельзя перескочить с первого пункта к четвёртому. Чем ярче диаграмма, тем важнее сделать этот разрыв видимым в записи.
Тот же принцип работает для БД. database-execution = 150 — не SQL diagnosis, не индекс и не бюджет. Это значение в условной шкале конкретного input. Оно может стать исходной точкой для отдельного вопроса, но в текущем пакете не получает рекомендаций. Такая неполнота является свойством качественного review: он не выдаёт отсутствующие причины за измеренные.
Связность, вложенность и параллельность
Complete tree проверяет минимальную структурную вещь: у дочернего span есть существующий parent, а время не идёт назад. Этого недостаточно, чтобы автоматически вычислить все возможные критические ветви. Мы не строим общий алгоритм распределённой трассировки и не интерпретируем overlap. В fixture интервалы заданы последовательно, потому что задача учебного review — показать разделение типов задержки, а не спрятать причинную ошибку за сложным графом.
Если два CHILD span пересеклись во времени, их нельзя бездумно сложить. Если один дочерний интервал выходит за root, это нарушение границы, а не «дополнительная производительность». Если producer и consumer разделены очередью, идентификаторы могут связывать события, но сам факт связи ещё не определяет время ожидания. W3C и OpenTelemetry помогают назвать связь и role; содержательный вывод всё равно требует явного measurement contract.
Почему короткий root не побеждает сам по себе
В incomparable-load-v1 root равен 800, то есть визуально лучше 1 000. Одновременно input меняет cohort, число logical requests, concurrency и input shape. Нельзя выбрать один из этих факторов как объяснение, потому что литерал не изолирует его. Возврат stop-incomparable-load не отрицает 800; он запрещает называть разницу эффектом предполагаемой оптимизации.
Контрольная граница не обязана быть большой статистической процедурой, чтобы быть полезной. Для данного пакета она предельно узка и проверяется равенством именованных полей. Это делает пример воспроизводимым и честным. В будущей системе граница может быть богаче, но принцип не меняется: сначала определить, какие условия должны совпасть, затем решить, допускает ли запись сравнительное высказывание.
Механика намеренно предпочитает отказ частичному ответу. Удобный, но неполный trace создаёт больше риска, чем отсутствие цифры: его легко включить в убедительный narrative. Named stop reason сохраняет контекст недостающего условия и делает повторный review дешевле. В этой модели отказ не является ошибкой функции; он и есть корректный результат для входа, который нельзя безопасно интерпретировать.
Поэтому `stop` должен быть виден и в коде, и в тексте, и в таблице, а не прятаться в комментарии к графику.
Зачем нужна единая trace, а не три полезных фрагмента
Отдельный лог БД, внешний таймер и очередь из другой записи могут быть качественными артефактами, но не составляют критический путь. В W3C trace context trace-id предназначен для идентификации распределённой trace; parent-id связывает операцию с родителем. В нашем узком упражнении это переводится в проверяемое дерево литералов. Отсутствующий link — запрет на склейку, а не повод подобрать наиболее похожее число.
Нагрузка не является фоном
Logical requests, concurrency и input shape здесь не «подписи к графику». Это условия, при которых одно synthetic наблюдение разрешено поставить рядом с baseline. Рост logical requests с 12 до 24 и concurrency с 3 до 6 меняет сам вопрос: короткий root в incomparable-load-v1 не говорит, улучшился ли тот же путь. Он говорит лишь, что задан другой литерал.
Полезная привычка — писать контрольную границу раньше результата. Тогда фраза «root стал короче» сразу вызывает проверку: тот же cohort? те же logical requests? та же форма входа? Если хотя бы на один вопрос нет точного ответа, результат следует записать как отдельное наблюдение. Не нужно подбирать коэффициент, нормировку или убедительное объяснение, чтобы снять этот запрет.
- Сначала проверить связность одного synthetic дерева и интервалы каждого named span.
- Затем выписать load boundary до просмотра отличий root interval.
- Потом отделить queue-wait от database execution и external dependency.
- Только после этих проверок оформить наблюдение или вернуть точную stop reason.
Что именно проверяет функция
Функция не вычисляет throughput и не моделирует scheduler. Она проверяет структуру аргумента: complete tree, названную queue-wait, фиксированную нагрузочную границу и отсутствие effect claim. После этого она ранжирует уже имеющиеся условные интервалы. Такой порядок полезен для review: сначала валидность материала, затем интерпретация, но никогда не обратная последовательность.
| Условие | Статус | Почему не продолжаем |
|---|---|---|
| дочерний span без parent | stop-incomplete-trace | путь нельзя собрать |
| cohort или concurrency иные | stop-incomparable-load | нет общей границы |
| длинный интервал unknown-delay | stop-hidden-queue | ожидание не отделено от работы |
| заявлен faster-after-change | stop-unsupported-effect | нет доказанного эффекта |
Граница утверждения
Даже complete и comparable input не утверждает, что найден bottleneck исправлен. Он разрешает один hand-off: «при заданных literals очередь — первая наблюдаемая кандидатная граница исследования». Рядом должны стоять условия сравнения и контрпример: при неизвестном ожидании или иной concurrency вывод отменяется. Это дешевле, чем спор о точности десятичного знака у несопоставимых наблюдений.
Проверяемые источники
- W3C Trace Context — версия: Recommendation, 23.11.2021, immutable publication. Фиксирует формат traceparent и смысл trace-id/parent-id для связности распределённой trace. Граница: Опираемся только на идентификацию и связь; не выводим производительность из стандарта.
- OpenTelemetry Specification Trace API — версия: v1.29.0, commit c6520a7, 11.01.2024. Задаёт различение CLIENT, SERVER, PRODUCER и CONSUMER span как описательных ролей. Граница: Роль span не доказывает причину задержки и не является метрикой.
- RFC 9110: HTTP Semantics — версия: RFC 9110, June 2022. Определяет HTTP как stateless application-level protocol и отделяет протокольную семантику от измерения времени. Граница: Не используем RFC для обещаний latency, capacity или реального поведения системы.