Хранить все трейсы — роскошь, которую не может себе позволить ни один прод. Сэмплирование решает, какие трейсы вообще будут записаны. 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.
Практика
- Выставьте
traceidratio 0.5на обоих сервисах; дёргайте /do 20 раз; посчитайте в UI, сколько трейсов сохранилось (≈50%, и каждый — полный). - Уберите parentbased (поставьте чистый traceidratio) и выставьте разные ratio на A (0.9) и B (0.1) — найдите в UI «обрубленные» трейсы и поймите причину.
- Сделайте вывод: почему parentbased — дефолт.
Мини-квиз
1. Кто принимает решение head-based? — Корневой сервис запроса; остальные следуют флагу в traceparent.
2. Трейс не попал в Jaeger, но p99-метрика его видела. Почему? — Метрики — по всем запросам; в хранилище трейсов попала только сэмплированная часть.
3. Что даёт adaptive sampling? — Вероятности по операциям от центра: горячим endpoint'ам — меньше, редким — больше, при фиксированном бюджете.