Наблюдаемость
Trace propagation: сшей сломанную систему в один trace
Читать про orphan-трейсы — не то же самое, что собрать фрагментированную систему обратно в одну картину. Построй небольшой многосервисный поток, посмотри, как он рассыпается в одно-span’овые orphan’ы на границах HTTP, async и очереди, затем закрой каждую брешь — и докажи фикс единственной метрикой, которая не врёт.
Преврати ментальную модель юнита в воспроизводимый инженерный цикл: заинструментируй propagation end-to-end, воспроизведи каждый класс orphan’ов, почини его на границе (заголовок, context.bind, inject/extract) и подтверди метрикой orphan-span rate плюс tail-sampling-ярусом, который держит каждый error-trace.
Построй многосервисный поток запроса, пересекающий HTTP-hop, внутрипроцессную async-границу и Kafka-очередь, затем сделай так, чтобы любой запрос пользователя появлялся как один связанный trace в течение 30s после завершения — снизив внутренний orphan-span rate от намеренно сломанного baseline до менее 1%, доказав измерением.
- Таблица orphan-span rate до/после по сервисам: сломанный baseline (каждая из трёх границ показана производящей orphan'ы) против починенного состояния ниже 1% для внутренних сервисов, измеренного из метрики, а не оценённого.
- Скриншот бэкенда или дамп span одного запроса пользователя, отрисованного как один связанный trace — gateway, HTTP-span воркера, span отложенной работы и span Kafka-консьюмера, все с одним trace-id и корректной цепочкой parent_id.
- Доказательство, что tail-sampling-ярус держит инъектированный error-trace и медленный trace, дропая ~99% baseline-трафика, с видимым в конфиге потолком num_traces и load-balancing-экспортёром, маршрутизирующим по trace-id.
- Абзац-разбор: первопричина каждого orphan'а и точный слой, которому принадлежал фикс (обёртка HTTP-клиента, context.bind, inject/extract) — и почему никакой sampling не смог бы починить lineage.
- Добавь CI-гейт: end-to-end-тест, прогоняющий запрос через все сервисы и проверяющий, что итоговый trace имеет ожидаемое число span, связанных одним trace-id, валя сборку при регрессии orphan rate.
- Добавь service mesh (Linkerd или Envoy) перед HTTP-hop, включи mesh-hop span и покажи три-span'овый вид (клиентское приложение, sidecar, серверное приложение) — затем докажи, что mesh всё равно не чинит Kafka-orphan.
- Добавь браузерный фронтенд, выпускающий начальный fetch через OTel-JS, и ограничь propagation traceparent тем же origin и явным CORS-allowlist'ом, чтобы заголовок не утекал на сторонние эндпоинты.
- Воспроизведи OOM долгоживущего trace: эмить trace, переживающий окно решения 30s, посмотри, как поздние span становятся orphan'ами, затем переработай его в span-linked под-трейсы и покажи, что RAM коллектора остаётся ограниченной.
Это цикл, который ты будешь запускать в каждом реальном инциденте propagation: инструментируй end-to-end, воспроизведи каждый класс orphan’ов на его границе и чини там, где он рождается — OTel-осведомлённый HTTP-клиент, context.bind через внутрипроцессную async-брешь, inject/extract через очередь — никогда не на дашборде или в сэмплере. Подтверждай метрикой orphan-span rate, единственной, которую OTel за тебя не покажет, и прогоняй оставленные трейсы через tail-sampling-ярус с потолком и маршрутизацией по trace-id. Сделав это раз на игрушечной системе, ты превращаешь production-версию, где брешь прячется квартал, в то, что ловишь за день.