Наблюдаемость
Head sampling и tail sampling: кто решает, какие трейсы выживают
Сервис обрабатывает 10 000 запросов в секунду. Хранить каждый трейс непомерно дорого. Но при случайном sampling в 1% один медленный запрос, вызвавший жалобу клиента, с вероятностью 99% будет тихо выброшен.
Head-based sampling
Решение keep/drop принимается на старте трейса — кодируется в бите 01 (sampled) trace-flags заголовка traceparent. Решение пропагируется на все downstream-сервисы, которые уважают его по умолчанию.
Типовые стратегии:
- Вероятностная: хранить N% трейсов (1–5% типично). Простая, предсказуемая, масштабируется линейно с трафиком.
- Rate-limiting: хранить не более K трейсов в секунду независимо от объёма трафика.
Стоимость: дёшево — решение принимается один раз на root span, до выполнения какой-либо работы. Несэмплированные запросы не генерируют span’ов вообще, экономя CPU, сеть и хранение.
Недостаток: решение слепо к исходу. Медленный или error-запрос, попавший в несэмплированные 99%, невидим в tracing-backend. Если инцидент затрагивает 0.5% трафика, а sampling 1% — сохранишь примерно половину инцидентных трейсов; но можешь не сохранить ни одного, если инцидент кратковременный.
Sampled-флаг: когда флаг 01, downstream-сервисы записывают и экспортируют span’ы. Когда 00, OTel SDK создают span’ы внутри, но по умолчанию не экспортируют. Это consistent sampling: либо весь трейс хранится, либо ничего — никогда фрагмент. Downstream-сервис может переопределить входящий флаг (например, всегда сэмплировать свои ошибки), но частичные переопределения дают фрагментарные трейсы, почти бесполезные для отладки.
Tail-based sampling
OTel Collector буферизует все span’ы для trace-id в течение настраиваемого decision-окна (30с–5мин), затем решает, хранить ли трейс, по policy:
- Есть ошибка → хранить 100%.
- Duration > порог → хранить 100%.
- Конкретный атрибут (например
user.tier=premium) → хранить 100%. - Вероятностный top-up → хранить 1% остального для базовой видимости.
Преимущества:
- Ловит каждый error-трейс даже при 0.1% трафика.
- Ловит каждый медленный трейс выше latency-порога.
- Даёт избирательность, делающую tail sampling доминирующим паттерном у high-traffic-сервисов.
Стоимость: collector обязан держать все span’ы каждого трейса в RAM до decision-time.
Модель памяти: active_traces × avg_spans_per_trace × bytes_per_span. При 50 000 in-flight трейсов × 100 span’ов × 1 KB на span = 5 GB RAM. Decision-окно напрямую управляет RAM-footprint’ом.
Требование load-balancing exporter: с несколькими репликами collector’а случайное распределение span’ов разбрасывает span’ы одного трейса по разным instance’ам. Каждый видит только фрагменты и не может принять корректное решение. Решение — load-balancing exporter, хэширующий по trace-id и направляющий все span’ы одного трейса на один и тот же collector instance. Это обязательно для корректной работы tail sampling.
| Параметр | Head sampling | Tail sampling |
|---|---|---|
| Момент решения | На старте трейса (head) | После завершения всех span’ов (tail) |
| Видит исход? | Нет | Да (ошибка, latency, атрибуты) |
| RAM collector’а | Минимальная | Пропорционально active_traces × span’ы × размер span’а |
| Требование роутинга | Нет (stateless) | Load-balancing exporter (хэш по trace-id) |
| Пропускает error-трейсы? | Да (с частотой 1 − sample%) | Нет (если error policy = 100%) |
Гибридный паттерн (доминирует в production)
Head-sample на 100% (каждый запрос входит в pipeline), затем tail-sample по policy:
- Error-трейсы → хранить 100%.
- Latency > 99-й перцентиль → хранить 100%.
- Всё остальное → 1% вероятностно.
Это даёт volume-контроль head sampling и избирательность tail sampling, за счёт одной дополнительной инфры: tier tail-sampling Collector с load-balancing exporter и достаточной RAM.
При 10k req/s с 30с decision-окном, 10 span’ами/трейс, 1 KB/span:
10 000 × 30 × 10 × 1 024 B ≈ 3 GB RAM collector’а. Реализуемо с 4–8 репликами collector’а.
Почему это работает
Гибридный паттерн — это почему «нужно хранить все error-трейсы» и «нельзя хранить всё» не взаимоисключающие. Head sampling пропускает каждый запрос без коммита хранения; tail-sampling tier затем применяет policy 100% для ошибок. Инженеры, пытающиеся решить это только head sampling, либо хранят всё (дорого), либо пропускают ошибки (ненадёжно). Двухуровневый дизайн разрешает оба ограничения.
- Типичная частота head sampling
- 0.5–5% трейсов
- Типичное decision-окно tail sampling
- 30с–5 мин
- RAM tail-sampler при 50k трейсов × 100 span × 1 KB
- ~5 GB
- RAM tail-sampler при 10k req/s, 30с окно, 10 span, 1 KB
- ~3 GB
- Load-balancing exporter: ключ роутинга
- хэш по trace-id
- Consistent sampling: трейс хранится/дропается
- 100% или 0% — никогда фрагмент
Команда выбирает tail-based sampling, чтобы хранить все error-трейсы. Какой операционный подвох нужно учесть?
traceparent приходит с sampled-флагом `00`. Что должен делать принимающий сервис по умолчанию?
Tail-sampling Collector OOM'ит каждые несколько часов. Метрики показывают стабильное число трейсов, но растущее число span'ов на трейс. Вероятная причина?
- 01Почему head sampling пропускает error-трейсы и какова частота пропуска?
- 02Объясни load-balancing exporter и почему tail sampling ломается без него.
- 03Опиши гибридный паттерн head-100% + tail-policy и когда действует каждый уровень.
Head sampling принимает решение keep/drop на старте трейса, используя sampled-флаг traceparent, пропагируя решение на все downstream-сервисы. Дёшево — несэмплированные запросы не генерируют span’ов вообще — но слепо к исходам: 1% head rate дропает 99% error-трейсов вместе с 99% нормальных. Tail sampling буферизует все span’ы в OTel Collector до закрытия decision-окна (30с–5мин), затем применяет policy: хранить все ошибки, хранить все медленные, хранить 1% baseline. Стоимость — RAM collector’а (active_traces × span’ы/трейс × байт/span) и обязательный load-balancing exporter, маршрутизирующий все span’ы одного трейса на один collector instance. Гибридный head-100% + tail-policy — production-стандарт: head при 100% подаёт всё в pipeline; tail tier решает, что персистировать.
- Согласованность sampling и tier tail-sampling Collectorsenior
- Async context на разных языках, service mesh, миграция B3 и безопасностьsenior
- Production-сбои propagation, span links и платформенный дизайнsenior
- Trace propagation: сшей сломанную систему в один tracesenior
- Trace propagation: тест с множественным выборомsenior
- Trace propagation: чтение кода и заголовковsenior
- Trace propagation: тест на свободное воспроизведениеsenior