В trace учебного request heavy-вариант пересёк allocation budget 6 и получил отметку gc-boundary. Рядом есть FFI и I/O boundaries, а invalid input уходит в error response. Самая дорогая ошибка здесь — назвать отметку реальной паузой, обвинить внешнюю систему без вызова или сразу менять конфигурацию runtime. Тогда исчезают и причина, и возможность безопасно откатить change. Цена ошибки — потратить время на изменение runtime без доказанного источника паузы.
Это не отчёт о production-инциденте. В нём нет реального профиля, сервера, D compiler, DRuntime, HTTP, foreign code или I/O. Есть один детерминированный in-memory request, два варианта одной обработки и error input. Его цель — собрать evidence в правильном порядке: result, stage trace, заданные units, выбранная boundary и только потом действие. Такая дисциплина полезна до того, как появятся цифры настоящего инструмента.
Собираем evidence до изменения runtime
Первый набор evidence небольшой: request id, нормализованный вход, success body либо error body, последовательность stages, allocation/work units модели, budget и список external boundaries. Значения, похожие на время, здесь запрещены: fixture не показывает миллисекунды, CPU или память процесса. Если соседняя система говорит о паузе, это отдельный факт с отдельным источником, а не расшифровка записи gc-boundary.
В valid варианте body остаётся {"requestId":"runtime-d-training-42","total":42}. Heavy-вариант получает 12 allocation units и не проходит budget 6. Reuse-вариант получает 3 units и проходит budget. Оба имеют 9 work units. Такое доказательство ещё не отвечает, какой код нужно менять в D. Оно только запрещает одновременно менять contract handler и считать, что уменьшение model units уже стало оптимизацией процесса.
| Наблюдение | Что фактически есть | Вероятная граница | Первое безопасное действие |
|---|---|---|---|
| heavy > budget | 12 units при budget 6, body равен reuse | учебные временные состояния | сохранить два trace и проверить локальный reuse change при том же output |
есть gc-boundary | пересечён порог 8 units, поле not-a-runtime-pause | граница для будущего profiling | не объявлять pause; выбрать реальный метод измерения отдельно |
| есть FFI/I/O boundary | invocation: not-performed | контракт внешнего вызова ещё не проверен | не обвинять зависимость; собрать owner, input/output и отдельный test |
| validation-error | status 400 и encode-error | ошибка входа до compute | сохранить error input и не применять success-path оптимизацию как исправление |
| result различается | success body не совпадает | нарушен contract handler | остановить локальную оптимизацию и вернуть равный результат прежде budget |
Читаем error route так же внимательно, как success
Проверка только счастливого запроса всегда оставляет дыру. В fixture invalid operation divide декодируется, отвергается validation и проходит через encode-error. Он не доходит до compute, FFI или I/O. Это даёт ясную отрицательную проверку: изменение buffer reuse не должно заставить ошибочный вход исчезнуть, стать успехом или неожиданно пересечь external boundary.
const report = runDRuntimeServiceFixture();
const failed = report.invalidRequest;
console.log(failed.response);
// { ok: false, status: 400, body: '{"error":"invalid-operation"}' }
console.log(failed.trace.map((entry) => entry.stage));
// decode, validation-error, encode-error
if (!failed.model.errorRouteVisible) {
throw new Error("error route disappeared from the training trace");
}
Такой error trace нужен и при разборе возможного allocation growth. Если heavy-вариант создаёт дополнительные units до validation, а invalid request больше не виден, нельзя сказать «мы сократили pressure». Возможно, мы просто перестали обрабатывать некорректный вход тем же контрактом. Поэтому fixture одновременно проверяет same handler result для success, явный status 400 для error и отсутствие compute/FFI/I/O в отрицательной ветке.
Отделяем условную GC-границу от причины паузы
У записи gc-boundary ровно одно значение: заданный счётчик пересёк заданный порог. Эта запись полезна, потому что показывает место, где команда договорилась задать фактический вопрос. Она не говорит, был ли GC, как он работал, остановил ли потоки, сколько длилась пауза и связана ли она с request. Документация D описывает automatic memory management, но переход от общего механизма к конкретному симптому всегда требует tool output и условий запуска.
Безопасный отчёт поэтому выглядит скромно: «в учебной модели 12 units пересекают budget 6; boundary помечена; фактического profile нет». Небезопасный отчёт добавляет к этому «из-за GC endpoint зависает». Второй текст звучит увереннее, но у него нет входа, версия, метод, график или trace реального процесса. Практическая работа начинается с первого текста: он позволяет назначить следующий эксперимент и не переписать систему по впечатлению.
Rollback-safe действие: откатываем гипотезу, не evidence
Если heavy и reuse возвращают один body, а отличается только model allocation, допустима маленькая обратимая правка: временное состояние в одном участке заменяется на повторное использование, а valid и invalid traces остаются рядом с change. Откат в таком случае — вернуть локальный путь и снова получить прежний trace. Нельзя называть rollback-safe глобальное отключение GC, смену allocator или переписывание foreign code без теста внешнего договора: такие действия расширяют границу и могут скрыть исходный симптом.
Если body различается, error route исчезает или boundary стала выполнять внешний вызов, действие другое: остановить оптимизацию, сохранить samples и восстановить прежний contract handler. Здесь важнее не сделать «быстрый» patch, а не потерять смысл ошибки. После этого можно отдельно решать, нужна ли новая схема данных, другой API или реальное измерение. Смешивать этот разбор с одной настройкой runtime нельзя: разная причина требует разного owner и обратимости.
| Подтверждённая ситуация | Что меняем | Что сохраняем | Чего не делаем |
|---|---|---|---|
| равный output, heavy выше budget, reuse внутри | один локальный temporary path | оба valid trace, budget и same-result assertion | не объявляем оптимизацию production без profile |
| условная GC boundary без real evidence | ничего в runtime | input, trace, версия будущего инструмента | не включаем/выключаем GC по модели |
| FFI/I/O boundary подозрительна | отдельный contract test границы | intent, owner, expected error/result | не приписываем внешнему коду вызов, которого не было |
| invalid input изменил маршрут | возвращаем validation/error contract | invalid sample и error trace | не сравниваем allocation до восстановления error route |
| успешный body изменился | останавливаем локальную правку | expected body и diff result | не прячем разницу за новой версией response |
Источники обозначают границы, а не готовый диагноз
Для терминов D я сверяю official language specification: automatic memory management, @nogc и ABI. Для исторической точки использован опубликованный до октября 2021 release D 2.097.2 и exact DRuntime source snapshot его тега. Ни один из этих источников не содержит profile данного request, потому что такого request не существовало. Поэтому они поддерживают только аккуратные утверждения о языке и границе ответственности.
Такой подход особенно важен для FFI. ABI объясняет, почему граница не исчезает из архитектуры, но не описывает particular foreign function, её ownership или возможный blocking. Аналогично источник о GC помогает назвать механизм, но не превращает 12 учебных units в измеренную pause. Чем выше соблазн сделать вывод о реальной среде, тем важнее отдельно сохранить command, версию, вход, платформу и исходные results.
Маршрут: симптом → причина → проверка → действие
- Симптом. Сохраните один success или error request вместе с result/body. Не исправляйте runtime до появления конкретного input.
- Причина-гипотеза. Разделите contract, model allocation, model GC boundary, FFI boundary и I/O boundary. Одно наблюдение не обязано объяснять остальные.
- Проверка результата. Сравните success body и status two variants; затем прогоните invalid input и убедитесь, что validation-error/encode-error сохранились.
- Проверка бюджета. Если result одинаков, проверьте allocation units против явно записанного budget. Смотрите на GC entry только как на пометку модели.
- Действие. При локальном budget difference меняйте один обратимый temporary path. При FFI/I/O выбирайте отдельный contract test. При разном result или error route сначала восстановите handler contract.
- Проверка после действия. Повторите те же valid и invalid samples, сохраните traces и только затем назначьте реальный profiler с описанным окружением.
Ограничения и следующий проверяемый шаг
Полевой маршрут не даёт production SLA, benchmark result, замер паузы, масштабирование, rate, memory footprint, сведения о compiler flags или DRuntime configuration. Он не исполняет D, FFI, file, network, HTTP, database, process, GC или profiler. Условные units не имеют единицы времени и не должны попадать в dashboard как metric. Откат в таблице — образец безопасного порядка, а не команда для чужой инфраструктуры.
Следующий проверяемый шаг для своего сервиса — написать один integration-level trace вокруг выбранного handler без чувствительных данных, сохранить valid и invalid inputs, а затем выбрать ровно одну реальную границу для измерения. Если trace показывает внешний вызов, сначала тестируем его договор. Если одновременно меняются result и units, возвращаем contract. Если остаётся один вопрос про allocation, тогда можно сравнить фактические данные в зафиксированной среде. Так расследование не обещает лишнего и оставляет путь к следующему доказательству.
Проверяемые источники
- D 2.097.2: source snapshot of Automatic Memory Management specification — неизменяемый исходник официальной спецификации из тега v2.097.2; он задаёт терминологию automatic memory management, но не профиль конкретного запущенного сервиса
- D 2.097.2: source snapshot of @nogc function specification — неизменяемый исходник официальной спецификации атрибута
@nogc; он ограничивает проверяемый D-код и не доказывает свойства внешней библиотеки или операции ввода-вывода - D 2.097.2: source snapshot of Application Binary Interface specification — неизменяемый исходник официальной границы ABI для взаимодействия с C ABI целевой системы; наличие границы не говорит о стоимости или безопасности конкретного foreign call
- D 2.097.2: официальный release record — выпуск опубликован 9 августа 2021 года и служит проверяемой дооктябрьской точкой для терминов; статья не выводит из номера версии benchmark или runtime profile
- DRuntime 2.097.2: immutable snapshot core/memory.d — точная фиксация исходника DRuntime, на которую указывал тег v2.097.2; используется для исторической проверки терминов, не как замена измерения приложения