Наблюдаемость
Production-сбои propagation, span links и платформенный дизайн
GitHub держал регрессию propagation целый квартал, когда 50% внутренних трейсов были orphan-спанами. Дашборды показывали трейсы всё это время. Никто не заметил, пока инженер не проверил orphan-span rate в рутинном ревью.
Реальные production-сбои propagation
Uber 2019: частичный rollout OTel привёл к тому, что 30% трейсов обрывались на границе между инструментированными и неинструментированными сервисами. Постмортем обязал добавить gate «ни один сервис не выходит в prod без W3C propagation», проверяемый в CI. Паттерн: инструментированные сервисы отдают идеальные спаны; неинструментированные отдают orphan-спаны; обе группы показывают разную глубину трейсов на дашбордах, но автоматических алертов на граничный сбой нет.
GitHub 2022: кастомная обёртка HTTP-клиента обходила хуки OTel и молча дропала traceparent у половины внутренних сервисов целый квартал — пока кто-то не заметил, что orphan-span rate вырос с 1% до 50%. Фикс — одна строка: обернуть клиент в OTel-aware версию. Урок: кастомные обёртки — самый частый пробел propagation в зрелых сервисах. Фикс всегда однострочный; найти его занимает квартал.
Slack 2023: tail-sampling Collector’ы упали по OOM и снесли pipeline трейсинга во время крупного инцидента — именно тогда, когда трейсинг был нужнее всего. Постмортем добавил лимиты num_traces и отдельный always-keep-тир для высокоприоритетных трейсов. Пробел мониторинга: health-метрики OTel Collector не были ни на одном SLO-дашборде.
Datadog 2024 (customer report): крупная Java-нагрузка использовала thread pool, который не переносил контекст через submitted tasks, поэтому 80% трейсов background-задач были orphan-спанами. Фикс: переключиться на executor с поддержкой CurrentTraceContext. Баг существовал месяцами; обнаружен в ходе квартального ревью orphan-rate.
Общий паттерн: баги propagation молчат. Дашборды продолжают показывать трейсы. Единственный механизм обнаружения — метрика orphan-span rate, и эта метрика должна быть на дашборде и желательно на алерте — OTel defaults её не выставляет автоматически.
Observability для самой propagation
Ключевые health-метрики propagation:
| Метрика | Норма | Сигнал когда |
|---|---|---|
orphan_span_rate по service.name | <1% (только entry-points) | Внутренний сервис >5% → регрессия propagation |
invalid_traceparent_received count | ~0 | Любой sustained rate → сломанный upstream |
trace_id_per_second | Пропорционально RPS × sample_rate | Резкий рост → свежие trace-id (propagation потеряна) |
broken_parent_count | <0.5% | Спаны, чей parent-id не встречается ни в одном другом спане того же трейса |
| Здоровое состояние | Порог | Действие по алерту |
|---|---|---|
| Orphan-спаны для внутренних сервисов | <1% | Page если >5% в течение 10 мин для конкретного сервиса |
| invalid_traceparent_received | <0,01% | Тикет если ненулевой rate держится >5 мин |
| broken_parent_count | <0,5% | Тикет если >2% в течение 10 мин |
Span links: когда дерево родитель-потомок ломается
Модель родитель-потомок предполагает линейную причинность: A вызывает B, B вызывает C. Это ломается в трёх сценариях:
- Batch-обработка: consumer вытягивает 1 000 сообщений из Kafka и обрабатывает их в одном батче. Нет единственного осмысленного «родителя» — 1 000 входящих trace-контекстов питают один batch-спан.
- Fan-in: несколько параллельных подзадач сходятся в точке join. Каждая подзадача — потомок собственной ветви; у точки join несколько причинных участников.
- Async follow-ups: исходящий запрос завершается и возвращает ответ пользователю, но порождает follow-up, который выполняется спустя часы. Контекст исходного запроса закрыт; follow-up’ам нужна причинная связь без того, чтобы быть потомками «мёртвого» спана.
Span-links в OTel решают всё это: спан объявляет дополнительные ссылки на SpanContext, с которыми он причинно связан, но от которых не наследуется. Tracing-бэкенды визуализируют links как пунктирные линии рядом с деревом родитель-потомок.
Senior-паттерн: любой трейс длиннее 30 секунд или шире 100 спанов — кандидат на рефакторинг с span-links. Разбейте длинный workflow на sub-трейсы, каждый из которых умещается в окно принятия решения tail-sampler’а, и используйте links для сохранения причинной цепочки. Трейсы остаются небольшими, sampler’у хорошо, следственная связь сохраняется.
Long-running трейсы и проблема 30 минут
Tail-sampler’ы имеют окна принятия решения 30 с–5 мин. Batch-задача, работающая 30 минут, отдаёт спаны уже после закрытия окна; поздние спаны выглядят для sampler’а как orphan-спаны.
Два production-паттерна:
- Разбить работу: разделить длинные workflows на sub-трейсы, связанные span-links, каждый умещается в окно принятия решения. Чистая архитектура, корректная по построению.
- Поддержка поздних спанов бэкендом: Tempo, Honeycomb и Datadog поддерживают поздно прибывшие спаны до 24 ч после старта трейса. Для long-running трейсов пропустить tail-sampling; использовать head sampling на 100% для batch-нагрузок. Практический retrofit для legacy batch-задач.
Окно принятия решения — это рычаг, который нужно настраивать, когда batch-нагрузки ломают tail-sampling. Увеличение его вверх увеличивает RAM Collector’а; правильный ответ чаще всего — разбить работу.
Обнаружен orphan rate 0,5% для внутреннего сервиса. Найдите первопричину.
Диагностика сломанного трейса по выводу tracing-бэкенда
# Запрос: trace_id == "4bf92f3577b34da6a3ce929d0e0e4736"
# Результат: 7 спанов
# сервис span_id parent_id длительность статус
1 api-gateway 1a2b3c4d5e6f7890 - 18ms OK
2 auth 7890abcdef123456 1a2b3c4d5e6f7890 14ms OK
3 inventory abcdef1234567890 1a2b3c4d5e6f7890 1200ms OK
4 payment fedcba0987654321 - 80ms OK # ORPHAN
5 postgres-client 1111222233334444 fedcba0987654321 55ms OK
6 email-job 5555666677778888 - 240ms OK # ORPHAN
7 audit-log 9999aaaabbbbcccc - 12ms OK # ORPHAN
# Также отдельные orphan-трейсы с одним спаном:
# trace_id 9981a... сервис payment, 78ms
# trace_id ab32c... сервис email-job, 280ms
# trace_id ff8e1... сервис audit-log, 14ms Трейс содержит 7 спанов, но 3 из них — orphan (нет parent_id), и существуют 3 отдельных трейса с одним спаном от тех же сервисов. Что происходит?
Спроектируйте end-to-end trace propagation для новой платформы с 30 микросервисами, браузерным frontend, Kafka backbone, service mesh и tier tail-sampling collector.
- Polyglot: 12 сервисов Node.js, 10 Java, 5 Go, 3 Python.
- Браузерный frontend (React) делает fetch-вызовы к API gateway.
- Kafka используется для async-обмена между 8 сервисами.
- Service mesh: Linkerd (Linux) для east-west HTTP и gRPC.
- Sampling: 100% ошибок, 100% медленных (>2 с), 1% baseline.
- On-call должен видеть любой пользовательский запрос как единый трейс в течение 30 с после завершения.
- W3C TraceContext + Baggage по умолчанию везде; B3 только для legacy interop, deprecated.
- OTel SDK регистрируется до старта приложения в каждом сервисе; CI gate это проверяет.
- Kafka, gRPC, mesh автоматически несут traceparent через auto-instrumentation.
- Async-границы (setTimeout, workers, callbacks) требуют явной дисциплины context.bind.
- Tail-sampling collector с load-balancing exporter для консистентности по trace-id.
- Правила sampling: 100% ошибок + 100% медленных + 1% baseline.
- Propagation имеет собственный observability-слой (orphan-span rate, invalid-traceparent count).
- Здоровый orphan-span rate (внутренние сервисы)
- ≤1% от всех спанов
- Здоровый invalid_traceparent rate
- ≤0,01%
- Здоровый broken-parent rate
- ≤0,5%
- Порог алерта: orphan rate внутреннего сервиса
- >5% в течение 10 мин
- GitHub 2022: orphan rate в момент обнаружения регрессии
- 50% (от baseline 1%)
- Время обнаружения регрессии GitHub без алертинга
- >1 квартала
Batch-процессор вытягивает 1 000 сообщений из Kafka и обрабатывает их в одной транзакции. Инженер моделирует это как один родительский спан с 1 000 дочерних спанов, по одному на сообщение. После деплоя tail-sampling Collector падает по OOM. Каков архитектурный фикс?
Production-команда добавляет алертинг на orphan-span rate. Алерт срабатывает для 'email-job' на уровне 6% (baseline 0,5%). Каков первый диагностический шаг?
- 01Объясните, зачем существуют span-links, и когда senior-инженер выбирает их вместо отношений родитель-потомок.
- 02Опишите три health-метрики propagation, которые должен мониторить каждый production tracing-деплой, и пороги алертов.
- 03Опишите 8-уровневый платформенный дизайн end-to-end propagation для polyglot-системы с 30 сервисами, Kafka, service mesh и tail sampling.
Production-сбои propagation молчат: Uber (30% сломанных трейсов месяцами), GitHub (50% orphan rate целый квартал), Slack (Collector OOM во время инцидента) и клиенты Datadog (80% orphan-спанов у background-задач) — все сбоили именно так. Общий паттерн: дашборды показывали трейсы, просто не связанные, и ни одна метрика не алертила на разрыв. Фикс — наблюдать health propagation через собственные RED-эквивалентные метрики: orphan-span rate по сервису, invalid-traceparent count, broken-parent rate — и алертить по ним. Span-links решают случаи, когда дерево родитель-потомок не работает: batch fan-in, async follow-ups, workflows шире, чем окно принятия решения. Long-running трейсы необходимо разбивать на sub-трейсы, связанные span-links, умещающиеся в окно Collector’а. Propagation — невидимый фундамент, от которого зависит каждая другая observability-фича; воспринимайте её health как первоклассную production-метрику.
встречается в40
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Инвалидация, dirty-биты и containmiddle
- Слои композитора: продвижение, перекрытие и память GPUmiddle
- Observability в проде: LoAF, INP и полная поверхность атакиsenior
- Hidden classes, деревья переходов и расположение в памятиmiddle
- V8 в production: Isolates, сжатие указателей и реальные аварииsenior
- Что такое воркеры и зачем они нужныjunior
- Механика web workers: dedicated, shared и OffscreenCanvasmiddle
- Structured clone и transferablesmiddle
- SharedArrayBuffer, Atomics и cross-origin isolationsenior
- Пулы воркеров, Comlink и наблюдаемость в продакшенеsenior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- JSONB, массивы и когда side table побеждаетmiddle
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Где происходит data fetching — и почему это решает LCPjunior
- React Server Components и Suspense streamingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Конверт IPjunior
- Читаем IP-заголовокmiddle
- Что делает TLS и зачем он нуженjunior
- Расписание ключей, SNI, ALPN и расширенияsenior
- Защита 0-RTT, ECH, гибридный PQ и продакшн TLSsenior
- Двенадцать слоёв: один URL, семь действующих лицjunior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior