Distributed Tracing
«Трейсинг у нас есть, им никто не пользуется». Я регулярно слышу это от команд, которые честно прошли весь путь: подключили библиотеку, подняли коллектор, платят за хранилище. В инциденте они по-прежнему идут в логи. Трейсы лежат рядом. Найти среди миллиона тот один, который объясняет поломку, никто не умеет.
Трейс — запись пути одного запроса через систему. Он состоит из спанов: у каждого есть начало, конец, имя операции и атрибуты, и каждый знает своего родителя. Чтобы спаны из разных сервисов сложились в одно дерево, вместе с запросом путешествует контекст — идентификатор трейса и идентификатор родительского спана. Всё остальное в этом листе про то, как не порвать эту цепочку, какие трейсы хранить и как ими потом пользоваться.
Границы с соседями. Metrics говорят, что стало хуже и насколько. Logging говорит, что случилось внутри одного сервиса. Трейс отвечает на третий вопрос: где именно запрос потерял время или сломался по дороге. Цена хранения и устройство семплирования разобраны в Telemetry Economics, а спаны на границах сервисов без правки кода даёт Service Mesh.
Что должен уметь
Заголовок раздела «Что должен уметь»Главный навык на уровне L4 — довести сервис до состояния, в котором трейс через него не рвётся. Я считаю его главным, потому что один сервис, который не передал контекст дальше, обесценивает работу всех остальных: дерево распадается на обрубки, и в инциденте видно два несвязанных куска вместо одного пути.
L3
- Читает трейс: находит самый длинный спан и отличает время работы сервиса от времени ожидания чужого ответа.
- Находит трейс по идентификатору из записи лога и обратно, записи лога по идентификатору трейса.
- Проверяет, что исходящие вызовы его сервиса несут заголовок с контекстом.
L4
- Инструментирует сервис: автоматические спаны на входящих и исходящих вызовах, ручные на операциях, которые важны для разбора.
- Выбирает атрибуты, по которым будут искать: версия сервиса, клиент, тип операции, результат.
- Передаёт контекст через очередь и связывает отложенную обработку с исходным запросом ссылкой между спанами.
L5
- Проектирует семплирование так, чтобы ошибки и медленные запросы сохранялись, а решение было одним для всего пути запроса.
- Находит разрывы: сервисы и прослойки, которые теряют контекст, и закрывает их по одному.
- Учит команду пользоваться трейсами в инциденте: от точки на графике к примеру трейса, от трейса к записям лога.
L6+
- Задаёт для организации один формат контекста, общий набор атрибутов и единый слой сбора.
- Решает, где хватает спанов на границах сервисов, а где нужно инструментирование внутри кода.
- Использует трейсы как источник карты зависимостей: по настоящему трафику, а не по схеме из документации.
Материалы
Заголовок раздела «Материалы»- Наталья Савенкова — SRE. Рецепты выживания в продакшне для инженера по надежности (2024). Читал и рекомендую. К этому листу относится один рецепт: сквозной идентификатор запроса пишется во всех компонентах. С него трейсинг и начинается, задолго до первого спана.
Статьи и доклады
Заголовок раздела «Статьи и доклады»- Benjamin H. Sigelman и соавторы — Dapper, a Large-Scale Distributed Systems Tracing Infrastructure (Google, 2010). Статья, из которой выросла вся нынешняя модель. Советую прочитать хотя бы аннотацию: три цели системы и два решения, которые сделали её возможной, перечислены там в одном абзаце.
- Grafana Labs — Faster incident response through distributed tracing: Inside Glovo’s use of Traces Drilldown (2025). Пересказ доклада инженеров Glovo с GrafanaCON. Блог вендора о своём продукте, и читать его стоит с этой поправкой, но ход расследования там описан по шагам, и он разобран ниже.
- OpenTelemetry — Traces и Context propagation. Две страницы, после которых модель понятна целиком: из чего состоит спан и как контекст едет между сервисами. Обязательно прочитать вторую, она короче и важнее.
- OpenTelemetry — Sampling. Разница между решением на входе и решением по итогу трейса, с честным списком издержек каждого.
Инструменты
Заголовок раздела «Инструменты»- OpenTelemetry — библиотеки инструментирования и коллектор. По моим наблюдениям, новый сервис сегодня чаще инструментируют им, а не библиотекой конкретного хранилища: хранилище можно сменить, не трогая код.
- Jaeger — хранилище и интерфейс для трейсов, проект CNCF. Вторая версия построена на основе коллектора OpenTelemetry. Имеет смысл, когда нужен отдельный инструмент под трейсы без остального стека.
- Grafana Tempo — хранилище трейсов из семейства Grafana. Его чаще берут там, где метрики и логи уже живут в Grafana и переход от графика к трейсу хочется делать в одном окне.
Best practices
Заголовок раздела «Best practices»Лучший известный мне публичный разбор того, что трейсы дают в инциденте, рассказали инженеры Glovo на GrafanaCON 2025. Ближе к часу дня, на пике заказов, у них упало число созданных заказов. Дашборд с метриками показал, что нижестоящий сервис ограничивает запросы на оформление, причиной оказалось свежее изменение, его откатили. Сервис вернулся в норму меньше чем за пять минут. На этом метрики своё дело сделали.
Дальше началось то, ради чего я пересказываю эту историю. Команда искала причину и долго шла по ложному следу. Логи указывали на базу данных. База при проверке оказалась здорова. Её проблемы были следствием. Трейсы у команды были, и поначалу они тоже не помогли. Помог способ их смотреть. Взяли трейсы каталога товаров, отфильтровали отказы, сравнили атрибуты ошибочных запросов с обычными и увидели одного клиента, у которого значения резко отличались от остальных. Потом открыли трейс из первых секунд инцидента. Картина сложилась такая: сервис оформления вызывал каталог, каталог ограничивал запросы, сервис повторял вызовы с долгими паузами и всё это время держал соединения из пула базы, и новым запросам соединений не хватало.
Из этой истории я забираю два вывода. Метрики погасили инцидент, а объяснили его трейсы, и это разные работы. И сами по себе собранные трейсы не объяснили ничего, пока не появился способ сравнивать их между собой. Узкое место трейсинга давно не в сборе.
Трейс рвётся на первом сервисе, который не передал контекст. Контекст едет в заголовке запроса. В OpenTelemetry по умолчанию это заголовок traceparent из спецификации W3C: версия, идентификатор трейса, идентификатор родительского спана и флаги, записанные через дефис. Библиотеки делают это сами для обычных вызовов по HTTP. Рвётся цепочка в местах, которые библиотека не видит: самодельный клиент, прокси, который пересобирает запрос, фоновая задача, запущенная из обработчика. Я проверяю это просто: беру один запрос, прохожу глазами его трейс и ищу сервис, после которого дерево обрывается.
Очередь — законное место разрыва, и для него есть свой механизм. Обработка сообщения может случиться через час после исходного запроса, и делать её дочерним спаном бессмысленно: родитель давно закончился. Идентификатор исходного трейса кладут в сообщение, а новый трейс ссылается на него связью между спанами. Так цепочка «заказ, списание, уведомление» остаётся читаемой, хотя состоит из трёх деревьев.
Атрибуты решают, найдётся ли нужный трейс. В истории Glovo причину нашли сравнением атрибутов: один клиент выделялся на фоне остальных. Если бы идентификатора клиента в спанах не было, сравнивать было бы нечего. Список я составляю от вопросов, которые задают в инциденте: какая версия, какой клиент, какой регион, чем закончилось. В отличие от меток метрик, атрибут спана может иметь сколько угодно значений, и это главное, чем трейс дополняет метрику.
Семплирование решает, какой трейс окажется под рукой. Хранить каждый трейс почти никто не может, и это не новость: в статье про Dapper семплирование названо одним из решений, без которых система не состоялась бы. Вопрос в том, где принимается решение. На входе оно дешёвое и слепое: о том, что запрос кончится ошибкой, ещё никто не знает. По итогу трейса оно зрячее и дорогое, потому что трейс приходится держать в памяти до конца. Подробности и цена разобраны в Telemetry Economics. Здесь мне важно одно: схему семплирования проверяют вопросом, сохранился ли трейс последнего инцидента.
У трейсинга есть границы, и первая из них самая частая. Системе из двух-трёх сервисов он не годится как первое вложение: путь запроса и так виден, а деньги и время уйдут в инфраструктуру сбора. Там полезнее сквозной идентификатор в логах. Вторая граница — чёрные ящики. База данных, внешний API и чужая очередь видны в трейсе одним спаном, и что происходило внутри, он не покажет. Третья — счёт. Трейс описывает один запрос и не отвечает на вопрос «сколько таких»; для этого рядом нужны метрики, и лучше со ссылкой от точки на графике к примеру трейса.
Связанные листья
Заголовок раздела «Связанные листья»- Metrics — откуда приходит сигнал, что пора открывать трейс; без ссылки от графика к примеру трейса два инструмента живут порознь.
- Logging — идентификатор трейса в каждой записи превращает лог и трейс в одно расследование.
- Telemetry Economics — сколько стоит хранение трейсов и как устроено семплирование на входе и по итогу.
- Service Mesh — спаны на границах сервисов без правки кода; контекст внутри сервиса передавать всё равно приходится самому.
- Systematic Troubleshooting — трейс как способ делить путь запроса пополам, не перебирая сервисы по очереди.
- Resilience Patterns — повторы и удержанные соединения из истории Glovo — случай для правил, которые разобраны там: задержка между повторами, таймауты, изоляция по отсекам.
- Composite SLO Methodology — карта зависимостей, построенная по трейсам, служит входом для расчёта составных целей.
Открытые вопросы
Заголовок раздела «Открытые вопросы»Как учить пользоваться трейсами, я для себя не решил. Инструментирование можно спустить сверху библиотекой и платформой. Привычку открывать трейс в инциденте спустить нельзя, и история Glovo показывает, что даже у команды с готовыми трейсами она появилась не сразу. Разборы прошлых инцидентов с трейсом на экране выглядят разумным способом, но данных, что он работает, у меня нет.
Рядом лежит инструментирование без правки кода. Проекты на eBPF обещают спаны без библиотек в сервисе, и для чужого или старого кода это заманчиво. Я не знаю, насколько такие трейсы пригодны для разбора, если в них нет атрибутов, которые знает только само приложение.
И вопрос про долю. Какой процент трейсов достаточно хранить, зависит от потока и от того, как часто случается редкое. Общего правила я не видел. Если у вас есть схема, которая пережила несколько инцидентов и ни разу не потеряла нужный трейс, расскажите через PR.