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

Модуль 05 / Урок 20 из 27

Tail-based сэмплирование

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

Tail-based решение принимается после завершения трейса: «записать все ошибочные и медленные, из остальных — 1%». Это компромисс между полнотой и стоимостью, но у него есть цена — память и сложность.

Как это работает

Процессор tail_sampling в OTel Collector: собирает спаны трейса, ждёт (decision_wait), затем применяет политики в порядке приоритета и выносит вердикт для трейса целиком:

processors:
  tail_sampling:
    decision_wait: 10s          # ждать завершения трейса
    num_traces: 50000           # ёмкость кэша решателя
    expected_new_traces_per_sec: 2000
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow
        type: latency
        latency: { threshold_ms: 1000 }
      - name: rare-service
        type: string_attribute
        string_attribute:
          key: service.name
          values: [legacy-billing]
      - name: base
        type: probabilistic
        probabilistic: { sampling_percentage: 2 }

Политики — ИЛИ: трейс сохраняется, если сработала любая. Схема применения: ошибки и медленные — 100%, всё прочее — 2%.

Доступные политики

ПолитикаЛогика
status_codeспаны с ERROR → записать трейс
latencyтрейсы дольше порога → записать
probabilisticбазовый процент для остального
rate_limitingжёсткий потолок трейсов/сек
string_attributeпо значению атрибута: сервис, tenant, флаг
numeric_attributeпо числовому атрибуту (например, размер ответа)
and / compositeкомбинации и вложенные политики

Цена и ограничения

  • Память: до решения все спаны живут в RAM Collector'а. Расчёт: num_traces × средний размер трейса; 50 000 × 20 КБ = 1 ГБ — уже ощутимо;
  • decision_wait: слишком мал — разрежем «медленные» трейсы (решение примется до их завершения); слишком велик — память и задержка появления в UI;
  • Один решатель на трейс: при нескольких репликах Collector'а спаны одного трейса могут попасть на разные реплики и трейс «порежется». Решение — балансировка по trace ID (load-balancing exporter: агент → LB-коллектор → tail-sampling пул);
  • Отказ Collector'а = потеря ожидаемых трейсов: tail-сэмплер держит их только в памяти.
💡 Практический рецепт: head-based 100% на агенте (не терять ничего по пути) → gateway-пул с tail_sampling (отсекать по правилам) → хранилище. Приложения ничего не знают о политике.

Head vs Tail — сводка

Head-basedTail-based
Где решениев SDK, до запросав Collector, после трейса
Гарантии—ошибки/медленные 100%
Ценанулеваяпамять, LB по trace ID, сложность
Когдапо умолчаниюкогда бюджет позволяет и нужна полнота ошибок

Практика

  1. Поставьте tail_sampling на свой Collector: errors 100%, latency > 500мс 100%, base 5%. Нагрузите сервис и убедитесь: ошибочные и медленные трейсы есть все, быстрых — каждый двадцатый.
  2. Подкрутите decision_wait при «медленном» endpoint'е (3с) и посмотрите на «обрубленные» трейсы; верните корректное значение.
  3. Разверните два реплики Collector за LB-экспортёром по trace ID и убедитесь в целостности трейсов.
  4. Замерьте память Collector'а при росте num_traces — составьте свою формулу бюджета.
Мини-квиз

1. Почему спаны одного трейса должны попадать на одну реплику tail-сэмплера? — Решение принимается по всему трейсу; разрезанный между репликами трейс решается неверно.

2. Что случится при слишком коротком decision_wait? — Часть спанов опоздает: трейсы обрублены, медленные не распознаны.

3. Почему при tail-схеме на SDK ставят always_on? — Чтобы в Collector поступали все трейсы — решать будет политика, а не случайность в SDK.