Дерево «родитель-ребёнок» — не единственная связь спанов. Для fan-out, батчей и фоновых работ существуют ссылки child-of и follows-from — и умение их выбирать отличает зрелую трассировку.
Два типа ссылок
| Ссылка | Смысл | Пример |
|---|---|---|
| ChildOf | родитель логически «ждёт» результата ребёнка (даже если параллельно): время ребёнка входит во время родителя | fan-out HTTP-вызовы внутри запроса; подзапросы батча, чей результат нужен ответу |
| FollowsFrom | родитель только «причинно предшествует»: его результат уже не влияет на исполнение; ребёнок живёт своей жизнью | отправка аудита в шину; кеширующий префетч после ответа; async-обработка события |
В OTel API различие почти стерлось (все ссылки задаются как SpanLink/parent),
но семантически выбор важен: он диктует, как читать трейс и как считать критический путь.
Fan-out: несколько родителей
Классика: агрегатор запускает 3 параллельных запроса. В визуализации это выглядит как несколько спанов-детей на одном уровне — но каждый ребёнок имеет одного parent. Для «спан с несколькими родителями» используются span links:
# Python: спан с дополнительной ссылкой на другой спан
from opentelemetry.trace import Link, SpanContext, TraceFlags
link = Link(other_span.get_span_context())
with tracer.start_as_current_span("merge_results", links=[link]):
Links полезны: «батч собран из N исходных запросов», «повтор после таймаута связан с исходным трейсом», «крон-задача обработала 100 трейсов прошлого дня». Ссылки видны в UI в панели спана и переходят по ним кликом.
Асинхронные задачи: главный кейс
Паттерн «запрос породил фоновую задачу» — самая частая точка потери связности:
- Один контекст на задачу: при постановке в очередь снабдите сообщение контекстом (traceparent в свойствах) — консюмер продолжит тот же трейс (урок 6.2);
- Задача живёт дольше трейса? Нормально: трейс завершён, «продолжение» — новый спан с FollowsFrom-ссылкой. Итог в UI: два трейса, связанные ссылкой;
- Планировщик: cron-джоба — корень собственного трейса (родитель — сам планировщик);
для читабельности добавляйте атрибут
schedule.name.
⚠️ Не «продлевайте» трейс бесконечно: трейс — история одного запроса. Многочасовая батч-джоба с 10 000 спанов в одном трейсе нечитаема — режьте на этапы-трейсы со ссылками между ними.
Практика
- Реализуйте fan-out: два параллельных вызова из одного endpoint'а; убедитесь, что в UI спаны на одном уровне и критический путь = max из них.
- Добавьте спан-линк: фоновая задача, обработавшая заказ, ссылается на исходный корневой спан; проверьте переход по ссылке в UI.
- Сделайте «батчовый» трейс: один спан-родитель + 100 детей-элементов — оцените читаемость, затем сравните с вариантом «агрегирующий спан + события» и сформулируйте правило выбора.
Мини-квиз
1. Аудит-сообщение ушло в шину после ответа пользователю: ChildOf или FollowsFrom? — FollowsFrom: родитель не ждёт, ответ уже отдан.
2. Что такое span link и когда нужен? — Дополнительная связь спана с чужим контекстом: батчи, повторы, слияние результатов.
3. Почему длинную джобу не трассируют одним трейсом? — Читаемость и инструменты: этапы — отдельные трейсы, связанные ссылками.