awesome-everything EN
↑ Обратно к восхождению

Наблюдаемость

Async context на разных языках, service mesh, миграция B3 и безопасность

Суть Каждый runtime имеет субстрат контекста — Node AsyncLocalStorage, Python contextvars, Java thread-local, Go context.Context — у каждого свои failure modes. Mesh пропагирует заголовки для HTTP, не для очередей. traceparent в браузере — privacy-риск без ограничения scope.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 16 min

Java-сервис мигрирует с thread-pool-based request handling на Project Loom virtual threads. После миграции 30% трейсов — orphan’ы. Версия OTel SDK не менялась. Что сломалось?

Async context propagation: язык за языком

Каждый runtime имеет свой механизм для «что такое текущий execution-контекст» — и OTel хукается в этот механизм. Когда пересекаешь примитив, через который runtime не несёт контекст автоматически, контекст тихо теряется.

Node.js: AsyncLocalStorage (встроена в Node 12+) — субстрат. OTel хукает её для in-process propagation. Подводные камни: setTimeout, setImmediate, queueMicrotask и любые third-party promise wrapping, создающие новый AsyncLocalStorage-контекст, могут потерять trace-контекст. OTel auto-instrumentation патчит распространённые, но custom-библиотеки ломают. Фикс: context.bind(ctx, fn) перед передачей callback’а на любую async-границу.

Python: contextvars из PEP 567 — субстрат; работает автоматически для asyncio, но не для threading без ContextVar.copy(). При ручном создании потоков OTel-контекст родительского потока не наследуется. Фикс: передавай текущий контекст дочернему потоку и устанавливай через context.attach(ctx) на входе.

Java: классический ThreadLocal плюс Span.makeCurrent() в try-with-resources. Project Loom virtual threads в большинстве случаев прозрачны, но требуют аккуратного вложения Scope — если virtual thread создаётся внутри Scope, scope обязан пережить virtual thread, иначе контекст закрывается пока поток ещё работает. Фикс: структурируй создание virtual thread внутри scope, не снаружи.

Go: явный context.Context везде — каждая функция принимает ctx, каждый span живёт в ctx. Go сделал правильный архитектурный выбор рано; контекст никогда не течёт неявно. Failure mode в Go — не случайная потеря контекста, а забытый ctx в цепочке вызовов функций. Фикс: передавай ctx везде; используй go vet и staticcheck для отметки отсутствующих параметров контекста.

Браузеры: zone.js (решение Angular для патчинга async-примитивов) или предложение TC39 AsyncContext; OTel-JS поддерживает оба через плагины. Service workers и Web Workers требуют явной передачи контекста.

RuntimeСубстрат контекстаТипичный failure modeФикс
Node.jsAsyncLocalStoragesetTimeout / custom async wrapperscontext.bind(ctx, fn)
Pythoncontextvars (PEP 567)Ручное threading обходит asyncioContextVar.copy() при spawn потока
JavaThreadLocal + ScopeLoom virtual threads переживают ScopeСоздавать virtual thread внутри Scope; использовать context-aware executor
GoЯвный context.Contextctx не протянут через функциюПередавать ctx везде; vet/staticcheck

B3 vs W3C: история миграции и безопасная последовательность

До W3C Trace Context, Twitter’s Zipkin/Brave использовали B3 с двумя вариантами: B3 multi-header (X-B3-TraceId, X-B3-SpanId, X-B3-Sampled как отдельные заголовки) и B3 single-header (все три в одном). Оригинальный B3 trace-id был 64-битный; расширен до 128 бит для совместимости с W3C.

Безопасная последовательность миграции:

  1. Аудит: найти каждый сервис, всё ещё эмитирующий только B3-заголовки.
  2. Развернуть composite propagator везде: зарегистрировать W3C TraceContext + B3 multi + B3 single на каждом сервисе. Писать исходящий только W3C; читать входящий из обоих. Это фаза «читать оба, писать W3C».
  3. Верификация: подтвердить, что orphan-span rate не регрессирует.
  4. Убрать B3 outbound: после подтверждения, что downstream-сервисы читают W3C, отключить B3 outbound у каждого upstream.
  5. Удалить B3 extractor: после квартала при нулевом B3 inbound заменить composite propagator на W3C-only.

Что ломается при пропуске шагов:

  • Пропуск шага 2: upstream отправляет W3C, downstream читает только B3 → трейсы расщепляются.
  • Пропуск шага 3: propagation-регрессия тихо длится неделями.
  • Пропуск шага 5: двойные байты заголовков на запрос бесконечно.

Trace-контекст через service mesh

Envoy, Linkerd, Istio, Cilium data plane участвуют в tracing двумя способами:

  1. Pass-through (всегда): sidecar прозрачно перенаправляет заголовки traceparent/tracestate/baggage на каждом HTTP и gRPC запросе.
  2. Эмитировать собственные span’ы (опционально, но рекомендуется): при включении sidecar создаёт span для сетевого hop’а, показывая sidecar-latency, connection pooling и TLS handshake timing отдельно от application-latency.

Конфигурация: mesh-proxy нужен адрес tracing-collector’а и решение по sampling (обычно — наследовать входящий флаг). Sampling-решение mesh’а должно совпадать с приложением; рассинхронизированные частоты дают несогласованные трейсы.

Ограничение: service mesh обрабатывает только HTTP и gRPC. Consumer’ы очередей, таймеры и fire-and-forget callback’и всё равно требуют явной application-level propagation. Mesh не замена OTel SDK instrumentation; он добавляет network-hop span, не заменяет application span’ы.

Почему это работает

Когда mesh эмитирует собственный span, получаешь трёхспановый вид одного HTTP-вызова: client app, mesh sidecar, server app. Это позволяет разграничить «приложение было медленным» от «mesh был медленным» — критическое различие при инцидентах с обновлениями sidecar, исчерпанием connection pool или штормами обновления mTLS-сертификатов.

Безопасность: trace-id как tracking-идентификатор

Trace-id уникальны на запрос, 128 бит энтропии, пропагируются в HTTP-заголовках и видны всем, кто может инспектировать трафик между клиентом и origin. Это делает их мощными debug-инструментами и столь же мощными потенциальными trackers.

Риск: если third party (CDN, marketing pixel, CSP-allowed analytics-сервис) может прочитать заголовок traceparent из исходящих запросов пользователя, он может коррелировать user-активность между сайтами, делящими одну tracing-инфру.

Смягчения:

  • W3C-спека рекомендует, чтобы user-facing-сервисы не пропагировали traceparent в response’ах (response — пользователю, не часть upstream-вызова).
  • Browser-side OTel SDK должны ограничивать propagation same-origin и явно разрешёнными CORS-origin’ами (список TraceContextPropagator.allowedOrigins).
  • Production-команды держат allowlist downstream-hostname’ов, получающих traceparent, и аудитируют квартально.
  • Baggage применяется идентично — всё в baggage наблюдаемо каждым downstream, включая third-party.
Production-числа trace propagation senior-уровня
Дефолтный OTel propagator
TraceContext + Baggage composite
B3 single-header ширина trace-id (оригинал)
64 бита (позже расширено до 128)
Overhead tracing service-mesh sidecar
~1–2% extra CPU
Байт заголовков на запрос (traceparent + tracestate малый)
~80–200 байт
W3C Trace Context Level 1
Recommendation 2020-02
W3C Trace Context Level 2
Recommendation 2024
Викторина

Команда мигрирует с B3 на W3C propagation. Они деплоят W3C-write на upstream-сервисах до деплоя W3C-read на downstream. Что происходит?

Викторина

Service mesh (Envoy) настроен пропагировать traceparent и эмитировать собственные mesh-hop span'ы. После включения команда всё равно видит orphan-трейсы для некоторых Kafka consumer'ов. Почему?

Вспомните перед уходом
  1. 01
    Java-сервис мигрирует на Project Loom virtual threads и orphan-span rate растёт до 30%. Диагностируй и фиксируй.
  2. 02
    Опиши безопасную 5-шаговую последовательность миграции с B3 на W3C TraceContext и что ломается при пропуске шага 2.
  3. 03
    Каков traceparent privacy-риск в браузерных приложениях и каковы три смягчения?
Итог

Propagation контекста в каждом runtime хукается в разный субстрат: AsyncLocalStorage в Node, contextvars в Python, ThreadLocal плюс Scope в Java, явный context.Context в Go. У каждого свой failure mode, когда пересекаешь примитив, через который runtime не несёт контекст автоматически — фикс всегда явная привязка контекста на этой границе. Миграция B3 на W3C требует read-both до write-W3C, верифицированного мониторингом orphan rate. Service mesh передаёт traceparent для HTTP/gRPC прозрачно и может эмитировать mesh-hop span’ы, но не инструментирует queue consumer’ов — они всё равно требуют application-level inject/extract. Заголовок traceparent в браузерных запросах — tracking-вектор при пропагировании к cross-origin third-party; ограничивай same-origin и явно разрешёнными CORS-origin’ами.

Связанные уроки
встречается в40
Продолжить восхождение ↑Production-сбои propagation, span links и платформенный дизайн
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.