Программа курса

Модуль 02 / Урок 9 из 27

Сэмплирование: head-based

Теория и практика

Хранить все трейсы — роскошь, которую не может себе позволить ни один прод. Сэмплирование решает, какие трейсы вообще будут записаны. Head-based решение принимается в момент начала запроса — и это решение делает весь трейс целиком.

Флаг в traceparent решает всё

Вспомните урок 1.2: в заголовке traceparent последний байт — флаги, бит «sampled». Если корень запроса решил «сэмплить», флаг ставится в 1 и распространяется по всей цепочке: все сервисы записывают свои спаны этого запроса. Если «не сэмплить» — никто не записывает. Именно так трейс остаётся цельным при любой политике.

traceparent: 00-4bf92f...4736-00f067...02b7-01   # sampled
traceparent: 00-4bf92f...4736-00f067...02b7-00   # not sampled

Стандартные стратегии (OTel)

СэмплерЛогикаПрименение
always_onсэмплировать всёдев, стенды
always_offничего (только для отладки инструментария)редко
traceidratio (probabilistic)по вероятности p на trace IDпрод по умолчанию: 1–10%
parentbased_*обёртка: уважать решение родителя, свои правила — только для корнейпочти всегда (дефолт OTel)
rate limitingне более N трейсов/сек на процесспотолок расхода
remote / adaptiveвероятности раздаёт сервер (Jaeger adaptive sampling)централизованное управление
# Переменные OTel (Java/Python/Node — одинаково):
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.05          # 5% трейсов

Почему parentbased обязателен

Если каждый сервис решает сэмплировать независимо (своё ratio), то сервис A решит «да», B — «нет»: у B корневой спан будет, а продолжение — нет. С parentbased_* некорневые сервисы всегда следуют входящему флагу и самостоятельное решение принимают только корень (обычно — gateway). Так один трейс либо полный, либо отсутствует.

Что вы теряете при probabilistic-сэмплировании

  • Редкие ошибки: упавший раз в час запрос с вероятностью 95% не попадёт в хранилище. Это главный аргумент за tail-based сэмплирование (урок 5.2);
  • Медленные хвосты: p99-запросы не гарантированно записаны;
  • Малые сервисы: при 5% и 10 RPS вы увидите трейс раз в несколько минут.

Компенсации: метрики считаются по всем запросам (сэмплится только хранение спанов); для редких ошибок — логи с trace_id; комбинация с ratelimit «на дне» для малых сервисов.

Adaptive sampling в Jaeger

Jaeger умеет раздавать вероятности по операциям: горячий endpoint — 1%, редкий — 100%, так чтобы суммарно сервис давал N трейсов/сек. SDK с сэмплером remote спрашивает вероятности у Jaeger (sampling manager) и кэширует. Это снимает ручную настройку ratio по десяткам endpoint'ов. Требует, чтобы у бэкенда было хранилище с поддержкой сэмплинг-данных (Cassandra/память).

Как выбирать ratio

  • Считайте не проценты, а трейсов в секунду: 100 тр/с × 15 спанов × 1 КБ ≈ 13 ГБ/сутки — уже серьёзное хранилище (подробная математика — урок 5.3);
  • Начните с dev: always_on; prod: 1–5% на трафик-тяжёлых сервисах, 100% на малых/критичных;
  • Ошибки — аргумент за tail sampling, а не за 100% head-based.

Практика

  1. Выставьте traceidratio 0.5 на обоих сервисах; дёргайте /do 20 раз; посчитайте в UI, сколько трейсов сохранилось (≈50%, и каждый — полный).
  2. Уберите parentbased (поставьте чистый traceidratio) и выставьте разные ratio на A (0.9) и B (0.1) — найдите в UI «обрубленные» трейсы и поймите причину.
  3. Сделайте вывод: почему parentbased — дефолт.
Мини-квиз

1. Кто принимает решение head-based? — Корневой сервис запроса; остальные следуют флагу в traceparent.

2. Трейс не попал в Jaeger, но p99-метрика его видела. Почему? — Метрики — по всем запросам; в хранилище трейсов попала только сэмплированная часть.

3. Что даёт adaptive sampling? — Вероятности по операциям от центра: горячим endpoint'ам — меньше, редким — больше, при фиксированном бюджете.