Короткий ответ
Единственно верного выбора нет — head-based sampling (решение о записи трейса принимается в самом начале запроса, до того как известен результат) проще и дешевле в реализации, но рискует не записать именно интересные (медленные, с ошибкой) трейсы при низком проценте sampling; tail-based sampling (решение принимается после завершения всех спанов, с учётом итогового результата) точнее отбирает важные трейсы, но требует буферизации всех спанов и значительно сложнее и дороже в инфраструктуре.
Как это работает подробнее
при head-based sampling решение "записывать этот trace_id или нет" принимается сразу на входе в систему (например, "записывать 1% запросов случайно"), и это решение распространяется на все последующие спаны через контекст трассировки — просто и дёшево, но при низком проценте большинство реально интересных случаев (с ошибкой или аномальной latency) просто не попадут в выборку. Tail-based sampling требует, чтобы коллектор (например, OpenTelemetry Collector с tail sampling processor) собрал и временно удержал в памяти все спаны трейса, прежде чем решить, сохранять его или отбросить, что требует больше ресурсов на буферизацию и усложняет маршрутизацию спанов одного трейса в один и тот же инстанс коллектора.
Компромисс: head-based — проще, дешевле, но может пропускать важные случаи при агрессивном sampling; tail-based — точнее отбирает ценные трейсы, но дороже в разработке и эксплуатации, требует дополнительных ресурсов на буферизацию. Гибридный подход (head-based с принудительным сохранением ошибок/аномалий) часто даёт приемлемый баланс. Выбор должен явно обсуждаться с учётом объёма трафика, бюджета на observability-инфраструктуру и критичности не пропустить редкие проблемные трейсы — кандидату стоит explicitly проговорить эти условия, а не считать один подход универсально лучшим.
Пример кода
# OpenTelemetry Collector: tail-based sampling processor (псевдоконфигурация)
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors-policy
type: status_code
status_code: { status_codes: [ERROR] }
- name: probabilistic-policy
type: probabilistic
probabilistic: { sampling_percentage: 2 }Что использовать на практике
- Практика. для систем с очень высоким трафиком и жёстким бюджетом на хранение head-based sampling с разумным процентом (например, 1–5%) плюс обязательное 100%-ное сохранение трейсов с ошибкой (biased sampling на уровне приложения: "если response status >= 500, принудительно sample") даёт неплохой баланс без полной сложности tail-based. Tail-based оправдан, когда критично гарантированно не терять именно редкие "интересные" случаи (аномальная latency, ошибки) и бюджет на дополнительную инфраструктуру коллекторов есть.
- Практика. Такая конфигурация гарантирует сохранение 100% трейсов с ошибкой и лишь 2% обычных успешных трейсов — при расследовании инцидента с ошибками исследователь получит полную выборку проблемных случаев, в то время как общий объём хранимых данных останется управляемым за счёт низкого процента на успешные запросы.
Подводные камни
- Частые ошибки. включать 100% sampling "для надёжности" в высоконагруженной системе без оценки реальной стоимости хранения и нагрузки на backend трассировки, что быстро становится неподъёмным по стоимости при росте трафика; использовать чистый head-based sampling с низким процентом без biased-правила для ошибок, из-за чего именно проблемные случаи систематически не попадают в выборку, когда они нужнее всего; недооценивать сложность роутинга спанов одного трейса в общий коллектор при tail-based подходе в горизонтально масштабируемой системе (без этого решение о сохранении трейса принимается по неполным данным).