8 lines
13 KiB
JSON
8 lines
13 KiB
JSON
{
|
||
"index": 35,
|
||
"slug": "editorial-2027-01-mechanism-debugging-decade",
|
||
"title": "Trace ID связывает события, но не доказывает причину",
|
||
"excerpt": "Как читать trace, log и metric вместе, проверять разрывы контекста и не принимать совпадение идентификатора за доказательство причины.",
|
||
"contentHtml": "<p>В двух журналах найден один trace ID. Временные метки почти совпадают. Один span длится дольше остальных. Команда объявляет его причиной задержки и меняет таймаут в этом сервисе. Через день задержка возвращается: запросы ждали соединение в шлюзе, а длинный span лишь включал это ожидание. Цена ошибки — потерянное время, лишний rollback и новый побочный эффект.</p>\n<p>Trace ID отвечает на вопрос «к каким данным относится эта запись?». Он не отвечает на вопрос «какая операция вызвала отказ или задержку?». Причинный вывод требует проверить структуру трассы, интервалы, статус, локальные журналы и путь, по которому запрос действительно прошёл.</p>\n<h2>Механизм: три сигнала и три разных вопроса</h2>\n<p>Log фиксирует событие в одном процессе: сообщение, локальное состояние, уровень и время. Span описывает операцию: начало, конец, родителя, сервис и атрибуты. Metric агрегирует много запросов и показывает частоту, распределение или долю ошибок. Один сигнал не заменяет другой.</p>\n<p>W3C Trace Context задаёт формат передачи <code>traceparent</code> и <code>tracestate</code> между HTTP-границами. Так разные сервисы могут продолжить общий контекст. Инструмент может только передать контекст, не создав подробный span. Очередь, фоновая задача, retry или библиотека без интеграции могут остаться за пределами записи.</p>\n<p>Поэтому trace ID создаёт область поиска, а parent/child-связи задают наблюдаемую структуру. Если у span нет родителя, это не доказывает, что операция независима. Возможны потеря записи, неверное поле, sampling или отдельная работа, ошибочно попавшая в trace. Отсутствие события в одном источнике означает только, что его там не нашли.</p>\n<figure><img src=\"/assets/editorial/2027/debugging-decade-2027-signal-tool-limit-table.svg\" alt=\"Сравнение log, span, metric и trace ID: сильная сторона сигнала, соседняя проверка и вывод, который нельзя сделать автоматически\" loading=\"lazy\" /><figcaption>Корреляционный ключ связывает записи. Причину подтверждает только согласованный набор независимых признаков.</figcaption></figure>\n<h2>Пример: найти разрыв, а не назначить виновника</h2>\n<p>Следующий код — учебный пример. Он работает с заранее заданным массивом и ничего не знает о production-трафике. Его задача — показать отрицательный путь: система должна явно отметить отсутствующего родителя, а не дорисовать целую цепочку.</p>\n<pre><code>const spans = [\n { traceId: 't-7', spanId: 'gateway', parentSpanId: '', service: 'gateway', durationMs: 22 },\n { traceId: 't-7', spanId: 'api', parentSpanId: 'gateway', service: 'api', durationMs: 81 },\n { traceId: 't-7', spanId: 'db', parentSpanId: 'missing', service: 'db', durationMs: 4 }\n];\n\nconst byId = new Map(spans.map((span) => [span.spanId, span]));\nconst links = spans.map((span) => ({\n service: span.service,\n parent: span.parentSpanId\n ? (byId.has(span.parentSpanId) ? 'present' : 'missing')\n : 'root'\n}));\n\nconsole.log(links);\n// gateway: root; api: present; db: missing</code></pre>\n<p>Результат даёт один проверяемый факт: у db нет родителя в принятом наборе. Он не говорит, что db вызвал задержку. Следующая проверка зависит от вопроса. Нужно узнать, потерялся ли span, не перепуталось ли поле parentSpanId, не создалась ли операция вне контекста и не отфильтровал ли сборщик запись.</p>\n<p>Длительность тоже требует контекста. Если gateway ждёт upstream 800 мс, эти 800 мс могут включать DNS, установку соединения, очередь, retry и чтение ответа. Долгий span показывает время, проведённое внутри его границ. Он не раскладывает это время по причинам без дочерних span-ов или дополнительных журналов.</p>\n<h2>Симптомы и проверяемые действия</h2>\n<div class=\"table-scroll\"><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>Один trace ID есть в gateway и API, но ответа нет</td><td>Сервис не записал span или запрос прервался до него</td><td>Сверить access log, статус соединения, sampling и окно времени</td><td>Отметить разрыв; не называть API причиной без записи операции</td></tr><tr><td>У span есть parentSpanId, но родителя нет</td><td>Потеря span, ошибка экспорта или неверная связь</td><td>Проверить полный экспорт, формат ID и дубликаты span-id</td><td>Исправить передачу или сбор; сохранить missing parent как сигнал</td></tr><tr><td>Самый длинный span совпал с пиком latency</td><td>Span включает ожидание upstream, retry или очередь</td><td>Сопоставить дочерние интервалы, status, retry count и метрику population</td><td>Разделить время по операциям; не оптимизировать сервис по одному trace</td></tr><tr><td>В log нет записи с нужным trace ID</td><td>Поле не попало в журнал, запись отбросил collector или выбран другой ID</td><td>Проверить схему, доставку, источник и request-id на границе</td><td>Считать источник неполным и продолжить по access/metric, не делать вывод об отсутствии события</td></tr></tbody></table></div>\n<h2>Действия по порядку</h2>\n<ol><li>Зафиксировать конверт симптома: метод, маршрут, статус, размер ответа, timestamp, длительность, trace ID и границу, на которой получена запись.</li><li>Проверить формат trace-id и span-id. Убедиться, что сервисы не меняют trace ID без явной новой границы и не смешивают его с request-id.</li><li>Построить граф parent/child. Отдельно отметить root, missing parent, duplicate span-id, пустой service.name и операции с разными trace ID.</li><li>Сверить start/end span-ов с локальными временными метками. Учесть clock skew, асинхронную передачу, retry, очередь и время ожидания соединения.</li><li>Сопоставить span с application log по span-id или request-id. Metric использовать для проверки масштаба: единичный trace должен быть сопоставим с общей картиной запросов.</li><li>Сформулировать узкий вывод. Например: «gateway наблюдал задержку чтения ответа» или «контекст потерян между API и worker». Не писать «API был причиной» без различающего доказательства.</li><li>Только после этого менять код, конфигурацию или лимит. Повторить тот же сценарий и проверить, исчез ли исходный симптом, не ухудшились ли соседние метрики и сохранился ли контекст.</li></ol>\n<h2>Ограничения</h2>\n<p>Sampling может исключить нужный span. Tail-based filtering может оставить только часть цепочки. Collector может получить события не по порядку или отбросить запись при перегрузке. Разные часы на узлах искажают сравнение timestamps. Асинхронный consumer может законно продолжить работу после завершения parent span. Для него нужны отдельные связи producer, сообщения и consumer.</p>\n<p>Trace не доказывает контрфактическое утверждение: нельзя по одной цепочке узнать, что произошло бы без конкретного вызова. Не стоит помещать персональные параметры в атрибуты и журналы. Корреляционный ключ должен помогать искать запись, а не раскрывать содержимое запроса.</p>\n<h2>Критерий готовности</h2>\n<p>Разбор готов, когда другой инженер получает один симптом и может повторить маршрут проверки без устной истории. В записи видны граница симптома, полный или явно неполный граф, проверенные интервалы, источник каждого вывода и отрицательный путь для отсутствующей записи. Исправление готово, когда повторный сценарий подтверждает изменение на исходном сигнале, не создаёт нового отказа по соседней метрике, а проверка missing parent или другого разрыва остаётся наблюдаемой.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.w3.org/TR/trace-context/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Trace Context</a> — Recommendation; формат и передача trace-контекста через HTTP-границы.</li><li><a href=\"https://opentelemetry.io/docs/concepts/signals/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: Signals</a> — официальное описание различий между traces, metrics и logs.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc5424.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 5424: The Syslog Protocol</a> — формальные части структурированного сообщения журнала; RFC не устанавливает причинность событий приложения.</li></ul>"
|
||
}
|