Пользователь нажимает кнопку «Купить» в интернет-магазине. Казалось бы, простое действие. Но за кулисами разворачивается длинная цепочка: проверка товара в каталоге, проверка пользователя, обращение к платёжной системе, резервирование на складе, отправка письма, обновление рекомендаций. Двенадцать сервисов, пять баз данных, три внешних интерфейса - и всё это должно отработать за доли секунды.
Теперь представьте, что что-то пошло не так. Заказ не оформился, пользователь получил ошибку 500, а вы не знаете, где именно порвалась цепочка. В монолитном приложении причину видно по стеку вызовов. В микросервисной архитектуре запрос проходит через десятки сервисов, каждый пишет свои журналы, и связать их в одну картину вручную практически нельзя.
Ровно эту задачу решает распределённая трассировка: она прослеживает путь каждого запроса через всю систему. Разберём, как она устроена, и отдельно - во что она обходится в железе, потому что об этом в обзорах пишут реже всего.
Микросервисная архитектура снимает часть проблем монолита, но создаёт свои - и почти все они про диагностику.
Отказы становятся частичными. В монолите приложение либо работает, либо нет. В распределённой системе пользователь может успешно войти, положить товар в корзину и не оформить заказ, потому что лёг платёжный сервис. Обычный мониторинг «доступен или недоступен» такую ситуацию не покажет: формально доступны все.
Отказы каскадируют. Кратковременный рост задержки в одном сервисе выбирает пул соединений в тех, кто к нему обращается, те начинают отвечать медленнее своим клиентам - и через полминуты медленно работает вся система, а исходная причина уже не очевидна.
Добавляется сеть. Сервисы общаются по сети, и её задержки, потери пакетов и кратковременные недоступности маскируются под проблемы приложения. Без отдельного наблюдения за сетевым взаимодействием непонятно, где заканчивается инфраструктура и начинается код.
Появляются промежуточные узлы. Балансировщики, прокси, служебная сеть (service mesh) - каждый добавляет свою задержку и свою точку отказа. Запрос может пройти через десяток узлов, прежде чем дойдёт до того, кто реально выполняет работу.
Трассировка работает по принципу «следования за запросом»: каждому запросу присваивается уникальный идентификатор, который передаётся дальше по всей цепочке и связывает разрозненные записи в одну картину.
Входящий запрос получает уникальный trace ID - идентификатор, который путешествует вместе с ним через все сервисы. Он добавляется в заголовки HTTP, в сообщения очередей, в записи журналов - везде, где компоненты передают работу друг другу.
Передача этого контекста между сервисами (на английском - propagation) требует поддержки со стороны кода. Готовые библиотеки делают это автоматически для популярных фреймворков, но самописные интеграции и нестандартные протоколы придётся дорабатывать руками.
Вместе с идентификатором можно передавать дополнительные сведения - идентификатор пользователя, версию приложения, признак тестовой группы. В терминологии стандарта это называется baggage, и злоупотреблять им не стоит: всё, что вы туда положите, поедет по сети с каждым запросом.
Каждая отдельная операция внутри трассировки описывается структурой span: когда началась, когда закончилась, что делала и с чем связана. Устоявшегося русского перевода нет, поэтому дальше используем это слово.
Span-ы вложены друг в друга и отражают вложенность операций: родительский описывает обработку HTTP-запроса целиком, дочерние - обращение к базе, вызов соседнего сервиса, выполнение бизнес-логики. Из этой вложенности и собирается дерево запроса.
К каждому span-у прикрепляют метки (структурированные пары «ключ - значение»: пользователь, версия, код ошибки) и события с отметкой времени (исключения, отладочные сообщения). Метки нужны, чтобы потом искать, события - чтобы понимать, что произошло внутри операции.
Трассировать все запросы подряд можно только на небольшой нагрузке. Дальше это упирается и в накладные расходы, и в объёмы хранения, поэтому применяют выборку - трассируют лишь часть запросов.
Выборка на входе решает судьбу запроса в самом начале: например, писать каждый десятый или не больше сотни трассировок в секунду. Просто, дёшево, предсказуемо по нагрузке. Минус очевиден: интересный запрос с ошибкой может просто не попасть в выборку.
Выборка на выходе дожидается, пока трассировка соберётся целиком, и только потом решает, сохранять ли её. Так можно оставлять все трассировки с ошибками и все медленные, а из успешных брать небольшую долю. Это именно то, что нужно для разбора инцидентов, но платить придётся памятью: незавершённые трассировки надо где-то держать до момента решения.
Практические ориентиры такие. До сотни запросов в секунду можно трассировать всё. На 100-1000 запросах в секунду обычно берут 10-25%. Выше тысячи - 1-5% на входе плюс выборка на выходе, которая вылавливает ошибки и медленные запросы.
Экосистема сложилась вокруг одного стандарта, и за последние годы вопрос «что выбрать» практически закрылся.
OpenTelemetry - результат слияния двух конкурировавших проектов, OpenTracing и OpenCensus. Оба к настоящему моменту закрыты: OpenTracing перевели в архив в январе 2022 года, OpenCensus - в июле 2023-го. Держать два конкурирующих стандарта на одну задачу отрасль сочла бессмысленным.
Сегодня это уже не «претендент на стандарт», как писали несколько лет назад. В мае 2026 года OpenTelemetry получил статус выпустившегося проекта CNCF - высшую степень зрелости в этом фонде - и по интенсивности разработки идёт вторым после Kubernetes. Практический вывод простой: если вы начинаете с нуля, брать нужно его, вопрос выбора стандарта больше не стоит.
Важно, что проект давно вышел за рамки трассировки: под одной спецификацией собраны трассировки, метрики и журналы. Благодаря этому не нужно тащить три разных агента на каждый сервер.
Работает связка так: в приложение добавляется библиотека, которая формирует span-ы, а дальше данные уходят в коллектор - отдельную службу, которая принимает телеметрию, обрабатывает её (фильтрует, обогащает, прореживает) и отправляет в хранилище. Коллектор понимает разные форматы на входе и умеет отдавать в разные системы на выходе.
Отсюда главное практическое достоинство: приложение инструментируется один раз, а систему хранения можно менять, не трогая код. Привязки к конкретному поставщику не возникает.
Jaeger, разработанный в Uber, ориентирован на высокую производительность и большие объёмы. Данные он складывает во внешние хранилища - Elasticsearch, Cassandra и другие.
Zipkin, созданный в Twitter, проще в развёртывании и вполне закрывает небольшие и средние установки.
Форматы данных у них совместимы, так что трассировки из одной системы можно передать в другую. При этом обе умеют принимать данные по протоколу OpenTelemetry, поэтому выбор хранилища сегодня почти не влияет на то, как инструментирован код.
Istio, Linkerd и другие решения класса service mesh дают трассировку без изменений в приложении: рядом с каждым сервисом работает прокси, который перехватывает весь сетевой трафик и сам проставляет трассировочные заголовки.
Плюсы - нулевые правки в коде, автоматический охват всех сетевых вызовов, настройка из одного места. Минус принципиальный: видно только то, что ходит по сети. Что происходит внутри сервиса - обращение к локальному кэшу, тяжёлый расчёт, ожидание блокировки - остаётся невидимым.
Поэтому на практике подходы комбинируют: сеть закрывают прокси, а критичные участки бизнес-логики размечают в коде руками.
Система трассировки собирается из нескольких слоёв, и у каждого своя задача.
Автоматическое подключается библиотекой и само оборачивает вызовы популярных фреймворков, драйверов баз данных и HTTP-клиентов. Работает без правки кода, но видит только то, что знает: самописную логику оно пропустит.
Ручное даёт полный контроль - разработчик сам решает, какие операции достойны отдельного span-а и какие сведения к нему приложить.
На практике берут и то и другое: автоматическое для стандартных операций, ручное - для бизнес-логики, ради которой всё и затевалось.
Агент работает рядом с приложением, принимает span-ы, копит их в буфере и отправляет пачками. Буферизация здесь не украшение: она отвязывает скорость приложения от скорости сети и хранилища.
Коллектор принимает данные от множества агентов, выполняет общую обработку - прореживание, фильтрацию, обогащение - и раскладывает в хранилища. Коллекторы масштабируются горизонтально: выросла нагрузка - добавили ещё один за балансировщиком, приложения об этом даже не узнают.
Данные трассировок - это поток записей с отметками времени, и хранят их в системах, заточенных под такой профиль: хорошее сжатие, быстрые выборки по диапазону времени, автоматическое удаление старого.
Отдельная задача - поиск. Чтобы находить трассировки по сервису, операции, пользователю или коду ошибки, нужны индексы, и именно они, а не сами span-ы, обычно определяют требования к дискам и памяти.
Данные разделяют по времени: свежие лежат на быстрых дисках и доступны для интерактивного разбора, старые уезжают на дешёвое хранение или удаляются по расписанию.
Собрать данные - половина дела. Дальше их надо превратить в понятную картину.
Классический вид трассировки - каскад полос, где каждая полоса это span, а её длина соответствует времени выполнения. Сразу видно, что за чем шло и что сколько заняло.
На такой диаграмме ищут критический путь - цепочку операций, которая и определяет общее время ответа. Оптимизировать имеет смысл именно её: ускорение того, что выполняется параллельно и завершается раньше, на итоговое время не повлияет.
Заодно видно обратное: операции, которые идут одна за другой, хотя могли бы выполняться одновременно. Это прямой резерв.
По накопленным трассировкам система сама строит карту: какой сервис к какому обращается, как часто и с какими задержками. Такая карта обычно расходится с той, что нарисована в документации, и расхождение само по себе полезно.
Сервисы с большим числом входящих связей - кандидаты в узкие места. Сервисы с большим числом исходящих - потенциальные источники каскадных отказов.
Средние значения времени ответа мало о чём говорят. Смотреть нужно на процентили: 95-й и 99-й показывают, что происходит у самых невезучих пользователей, и именно эти цифры определяют, считают ли систему быстрой.
Сравнение показателей между версиями приложения показывает, что изменение реально сделало с производительностью, а не что о нём думали при выкатке.
Система трассировки сама потребляет ресурсы и сама влияет на то, за чем наблюдает.
Накладные расходы на инструментирование. Формирование span-ов добавляет работу на каждую операцию. Современные библиотеки делают это дёшево, но на высокой нагрузке даже доли миллисекунды складываются в заметную величину.
Основной приём борьбы - асинхронная отправка: span-ы копятся в памяти и уходят в фоне, а обработка запроса их не ждёт. Это переносит нагрузку с задержки на память, что почти всегда выгодный обмен.
Адаптивная выборка снижает долю трассируемых запросов при росте нагрузки. Логика простая: пока система здорова, подробности не нужны, а когда ей плохо - нужны, но именно тогда ресурсов меньше всего.
Разделение горячих и холодных данных. Свежие трассировки нужны быстро и часто, месячной давности - редко и медленно. Старые можно агрегировать до статистики, сохранив выводы и выбросив подробности.
Об этом в обзорах пишут реже всего, а вопрос при внедрении возникает первым. Посчитаем на конкретном примере.
Возьмём систему на 100 запросов в секунду, где один запрос порождает в среднем 10 span-ов, а один span в сериализованном виде занимает около 2 КБ. Это типичные для веб-приложения величины.
| Режим | Объём в сутки | За неделю |
|---|---|---|
| Трассировать всё (100%) | ~170 ГБ | ~1,2 ТБ |
| Выборка 10% | ~17 ГБ | ~120 ГБ |
| Выборка 1% | ~1,7 ГБ | ~12 ГБ |
Цифры отрезвляющие: разница между «пишем всё» и «пишем каждый десятый» - это разница между терабайтом в неделю и сотней гигабайт. При этом на десяти процентах вы по-прежнему видите картину системы, а с выборкой на выходе ещё и не теряете ошибки.
Практический вывод, который экономит деньги: на масштабе малого и среднего бизнеса узкое место - не процессор, а диск и срок хранения. Один коллектор на одном ядре переваривает порядка 15 тысяч span-ов в секунду, то есть в нашем примере он не загружен и на десятую часть даже без выборки. А вот хранилище растёт линейно и не прощает забытого срока хранения.
Что до самого коллектора, ориентиры такие: соотношение примерно одно ядро на 2 ГБ оперативной памяти, а при развёртывании агентом рядом с приложением хватает четверти ядра и 256-512 МБ. Коллектор чувствительнее к памяти, чем к процессору, поэтому ограничение по памяти в его настройках выставляют явно - иначе при всплеске нагрузки он вырастет и будет остановлен системой.
Отсюда практический порядок внедрения: сначала определите срок хранения, потом долю выборки, и только потом считайте железо. Обратный порядок приводит к тому, что через месяц хранилище заканчивается в самый неподходящий момент.
Как считать ресурсы под такие задачи в целом, разбирали в материале про планирование CPU, RAM и хранилища для виртуальных машин.
Трассировка не заменяет остальной мониторинг, а закрывает его слепое пятно. Метрики отвечают на вопрос «что-то сломалось?», журналы - «что именно написала система?», трассировки - «где в цепочке это произошло?». Порознь каждый инструмент оставляет вопросы.
Практика связывания простая: добавьте trace ID в журналы приложений. Тогда из медленной трассировки можно одним переходом попасть в записи журнала ровно того запроса, а не искать по времени в общем потоке. Это самая дешёвая интеграция из возможных и одновременно самая полезная.
Метрики удобно снимать с тех же данных: по span-ам считаются частота запросов, доля ошибок и время выполнения по каждой операции - тот самый набор, который в отрасли называют золотыми сигналами. Отдельно инструментировать приложение ради них не нужно.
Как устроен сбор и визуализация метрик на этом же стеке, разбирали в статье про Prometheus и Grafana, а более широкий взгляд на наблюдение за приложениями - в материале про мониторинг производительности приложений.
И последнее, о чём стоит помнить при внедрении. Трассировка окупается там, где в цепочке действительно много участников. Если у вас три сервиса и одна база, скорее всего, хватит журналов с общим идентификатором запроса и обычных метрик - а разворачивать коллектор, хранилище и поиск ради трёх сервисов будет дороже, чем польза от них.
Как наблюдать за самими виртуальными машинами и контейнерами, на которых всё это работает, - в материале про мониторинг виртуальных машин и контейнеров. Чем агентный сбор данных отличается от безагентного и что выбрать - в разборе про агентный и безагентный мониторинг.
Считаете железо под систему мониторинга или высоконагруженное приложение?
Инженеры ITTELO помогут подобрать конфигурацию под ваши объёмы данных и срок хранения, подскажут по дисковой подсистеме и памяти, соберут и протестируют сервер под задачу перед отгрузкой, с гарантией и поддержкой после продажи. На рынке серверов 11+ лет.
Серверы под высокую нагрузку · +7 (800) 551-80-12 · info@ittelo.ru