Пошаговый разбор расследования ошибок в микросервисах: граф сервисов, хронология трейсов, корреляция логов и структурированная отладка на примере Uptrace

Введение

В микросервисной архитектуре один падающий эндпоинт может скрывать проблему в совершенно другом сервисе. В этой статье я покажу пошаговый процесс расследования реального инцидента: от обнаружения 100% error rate до точной причины сбоя — и всё это менее чем за минуту.

Мы будем использовать Uptrace — OpenTelemetry-native платформу для трейсинга и мониторинга. Все примеры основаны на реальном demo-приложении с микросервисами.

Что вы узнаете:

  • Как использовать service graph для поиска bottleneck

  • Техники фильтрации spans для быстрого углублённого анализа

  • Как восстановление хронологии помогает найти узкое место

  • Корреляция трейсов и логов для анализа первопричины

  • От расследования к предотвращению через мониторинг

Симптом: проблемный эндпоинт

Представим ситуацию: пользователи жалуются, что рекомендации не загружаются. Открываем APM dashboard и сразу видим проблему:

Endpoint: GET /api/recommendations
Error rate: 100% 🔴
Latency p90: 10.6ms
Rate: 1.9 spans/min
APM Overview - Slowest endpoints: GET /api/recommendations с 100% error rate
APM Overview - Slowest endpoints: GET /api/recommendations с 100% error rate

100% error rate — это критично. Каждый запрос падает. Но почему?

Phase 1: Service Graph — где проблема?

Первый шаг — понять где именно в цепочке сервисов происходит сбой. Для этого используем service graph.

Что такое service graph?

Service graph визуализирует, как сервисы вызывают друг друга. Каждый узел — это сервис, каждое ребро — вызов между сервисами.

Пример нашего графа:

[frontend] ---> [recommendation] ---> [product-catalog]
   ✅              🔴 100% errors               ❓
Service Graph - визуализация микросервисной архитектуры
Service Graph - визуализация микросервисной архитектуры

Граф сервисов: красные узлы (frontend, recommendation) сигнализируют об ошибках. Зелёные (product-reviews, cart) работают в норме. На рёбрах видна задержка и процент ошибок

Уже видно, что:

  • frontend делает запросы (красный узел — есть проблемы)

  • recommendation возвращает ошибки 🔴

  • Зелёные узлы (product-reviews, cart) работают нормально ✅

  • Проблема где-то внутри или дальше по цепочке

Incoming vs Outgoing perspective

Service graph можно смотреть в двух режимах:

Incoming — кто вызывает сервис (зависимости на сервис)
Outgoing — какие зависимости ждёт сервис

Переключаясь между ними, видим:

  • Incoming: frontend → recommendation (6.1ms duration, errors)

  • Outgoing: recommendation → ??? (здесь и проблема)

Вывод Phase 1: Проблема в recommendation — он не может достучаться до своих зависимостей.

Phase 2: Анализ проблемных spans

Теперь нужно посмотреть на конкретные запросы. Для этого переходим к spans.

Фильтрация: убираем шум

В production-системе миллионы spans. Нужна точная фильтрация:

Шаг 1: Фокус на одном сервисе

service_name = frontend

Шаг 2: Только server spans (обработанные запросы)

_kind = server

Шаг 3: Только ошибки

_status_code = error

Теперь видим только проблемные запросы от frontend.

Aggregations: понимаем масштаб

Группируем spans по операциям и смотрим aggregations:

Aggregations:
  - perMin(count())     # сколько запросов в минуту
  - p99(_dur_ms)        # 99-й процентиль latency
  - _error_rate         # процент ошибок

Group by:
  - _group_id           # группировка одинаковых операций

Результат:

Operation

Rate

p99 latency

Status

GET /api/recommendations

2.1/min

16ms

🔴 Errors

POST /api/checkout

1.4/min

23ms

🔴 Errors

GET /api/products/{productId}

11/min

5.7ms

🔴 Errors

Spans Groups с фильтрами: 5 групп проблемных эндпоинтов
Spans Groups с фильтрами: 5 групп проблемных эндпоинтов

Spans → Groups: три активных фильтра (service_name, kind, status_code) сузили список до 5 групп. Агрегации perMin(count()), p99(_dur_ms) и error rate показывают масштаб проблемы

Видим, что проблема затрагивает несколько операций, но GET /api/recommendations показывает стабильный error pattern. Давайте углубимся в эту операцию и посмотрим конкретные запросы.

Проблема изолирована — можно сосредоточиться на конкретных операциях!

Phase 2.5: Выбор конкретного span

Кликаем на одну из строк GET /api/recommendations в таблице Groups. Открывается детальная панель с информацией об этом конкретном запросе.

Детальная панель span с атрибутами ATTRS и кнопкой VIEW TRACE
Детальная панель span с атрибутами ATTRS и кнопкой VIEW TRACE

Клик на GET /api/recommendations открывает боковую панель. Вкладка ATTRS показывает все структурированные атрибуты в формате key-value. Кнопка VIEW TRACE ведёт к полной хронологии запроса

Выбор конкретного span открывает детальный view с его атрибутами в виде structured key-value pairs. Это позволяет легко фильтровать и искать проблемы.

Что мы видим в атрибутах:

  • http_status_code: 500 — подтверждает ошибку сервера

  • http_route: /api/recommendations — конкретный endpoint

  • http_target — полный URL с query-параметрами

  • host_name: play-all-in-one — на каком хосте запущен сервис

  • next_span_name — следующий span в цепочке вызовов

Это structured logging в действии — каждое поле имеет имя и тип, что делает отладку намного эффективнее, чем работа с обычными текстовыми логами.

Теперь кликаем VIEW TRACE, чтобы увидеть полную картину запроса через все микросервисы.

Phase 3: Trace timeline — где узкое место?

После клика на VIEW TRACE открывается waterfall timeline — полная история одного запроса через все микросервисы.

Что такое trace?

Trace — это полная история одного запроса через все микросервисы. Визуализируется как waterfall timeline.

Пример нашего trace:

GET /api/recommendations                                [9.5ms total]
├─ user_get_recommendations (load-generator)           [0.7ms]
│  └─ GET http://frontend-proxy:8080/...               [1.3ms]
│     └─ Ingress (frontend-proxy)                      [0.2ms]
│        └─ router frontend egress                     [0.8ms]
│           └─ GET http://frontend-proxy:8080/...      [0.3ms]
│              └─ GET /api/recommendations (frontend)  [6.4ms] 🔴
│                 └─ rpc:recommendation                [failures]
Trace waterfall - хронология запроса
Trace waterfall - хронология запроса

Waterfall view trace: путь запроса от load-generator через proxy до frontend. Последний span (6.4ms) — явный виновник задержки и ошибок

Timeline analysis:

  • Total duration: 9.5ms

  • Load generator: 0.7ms (инициация запроса)

  • HTTP calls через proxy: ~2ms

  • Frontend processing: 6.4ms — здесь основное время! 🔴

    • Из них значительная часть уходит на rpc:recommendation (26% от total)

    • Видны ошибки (красные маркеры)

Вывод Phase 3: Bottleneck в GET /api/recommendations на frontend (6.4ms из 9.5ms). Запрос проходит через несколько переходов (load-generator → proxy → frontend), но основное время и ошибки происходят на финальном этапе при вызове recommendation service.

Детальный просмотр проблемного span

Кликаем на самый длинный span GET /api/recommendations (6.4ms) в waterfall. Открывается детальная панель справа с полной информацией об этом span.

Детальные атрибуты проблемного span в waterfall
Детальные атрибуты проблемного span в waterfall

Панель атрибутов самого долгого span: статус error, http_status_code = 500, длительность 6.4ms. Видны все метаданные сервиса: хост, ОС, версия библиотеки Next.js

Этот span показывает где именно происходит ошибка — frontend обрабатывает запрос /api/recommendations, но получает 500 error.

Ключевые атрибуты:

  • http_status_code: 500 — сервер вернул internal server error

  • http_target — полный URL с параметром productIds=L9ECAV7KIM

  • next_span_name: GET /api/recommendations — следующий вызов в цепочке

  • next_span_type: BaseServer.handleRequest — тип обработчика запроса

  • otel_library_name: next.js — используется Next.js framework

Теперь нужно понять почему этот span падает. Для этого переходим к логам, связанным с этим трейсом.

Phase 4: Logs — первопричина

Трейс показал где проблема. Теперь нужно понять почему. Для этого смотрим логи, связанные с трейсом.

Корреляция трейсов и логов

В Uptrace трейсы и логи связаны через trace_id. Не нужно отдельно искать логи по времени или сервису — достаточно, находясь в waterfall view, кликнуть на вкладку LOGS & ERRORS (7), и все логи этого конкретного запроса появятся автоматически.

Вкладка LOGS & ERRORS - 7 записей логов привязанных к trace
Вкладка LOGS & ERRORS - 7 записей логов привязанных к trace

Вкладка «LOGS & ERRORS (7)»: все 7 записей относятся к одному trace_id. Повторяющееся сообщение «DNS resolution failed for product-catalog:3550» с gRPC статусом UNAVAILABLE (код 14) — это и есть первопричина

Что видим в логах:

Повторяющееся сообщение об ошибке появляется много раз с одинаковым содержанием:

Exception: _InactiveRpcError of RPC that terminated with:
  status = StatusCode.UNAVAILABLE
  details = "DNS resolution failed for product-catalog:3550;
            C-ares status is not ARES_SUCCESS
            qtype=A name=product-catalog is_balancer=0;
            DNS server returned general failure"
  grpc_status: 14
  grpc_message: "DNS resolution failed for product-catalog:3550"

Также видим HTTP access log, показывающий результат:

GET /api/recommendations?productIds=L9ECAV7KIM HTTP/1.1
Status: 500
User-Agent: python-requests/2.32.4

Каждая запись лога связана с трейсом через trace_id. Клик по иконке ссылки открывает полные структурированные атрибуты записи в формате key-value

Первопричина найдена!

Проблема: DNS не может разрезолвить product-catalog:3550

Технические детали:

  • gRPC Status: UNAVAILABLE (code 14)

  • DNS Query: A record для product-catalog

  • DNS Resolver: C-ares (асинхронный DNS resolver)

  • Result: "DNS server returned general failure"

Возможные причины:

  • Сервис product-catalog не запущен

  • Неправильный DNS record в Kubernetes/service mesh

  • Network policy блокирует доступ к DNS

  • Неправильный hostname в конфиге (должен быть product-catalog.namespace.svc.cluster.local)

  • DNS server перегружен или недоступен

Время от симптома до первопричины: ~60 секунд ⏱️

Phase 5: Предотвращение через monitoring

Расследование закончено — мы нашли первопричину. В production-среде следующим шагом было бы исправление (исправить DNS record, перезапустить сервис, обновить конфигурацию). Но важно не только исправить текущую проблему, но и предотвратить повторение. Для этого создаём монитор.

От trace к monitor:

  1. Возвращаемся на вкладку TRACE

  2. Кликаем на проблемный span GET /api/recommendations (6.4ms) в waterfall

  3. Справа открывается детальная панель span

  4. Кликаем на dropdown MONITOR

  5. Выбираем Monitor p99 duration

Dropdown MONITOR с вариантами мониторинга span
Dropdown MONITOR с вариантами мониторинга span

Кнопка MONITOR в панели span открывает меню из 7 вариантов. Выбираем «Monitor p99 duration» — Uptrace автоматически заполнит все поля формы из атрибутов текущего span

После выбора Monitor p99 duration Uptrace автоматически создаёт форму с предзаполненными полями из span.

Что автоматически заполнено:

Monitor: httpserver:frontend > GET /api/recommendations p99 duration

Metric: uptrace_tracing_spans
Aggregation: p99($spans)
Filter:
  - _group_id = "8037161693486813802"
  (это ID группы spans для данной операции)

Alert threshold: p99 latency > 15ms
Check interval: last 5 points (5 minutes)
Notification: Email to EVERYONE
Max allowed latency
Max allowed latency

Зелёная зона на графике показывает допустимый диапазон (до 15ms). Когда p99 latency превышает этот порог — монитор создаёт уведомление автоматически.

Теперь если p99 latency превысит 15ms — команда получит автоматический alert и сможет среагировать до того, как проблема станет критичной. Монитор можно настроить на отправку уведомлений в Slack, Telegram, PagerDuty или по email.

При срабатывании, уведомление высылается в TG
При срабатывании, уведомление высылается в TG

Подсказки

1. Structured filtering

Не смотри на все spans сразу. Фильтруй:

  • service_name — один сервис

  • _kind = server — только обработанные запросы

  • statuscode = error — только ошибки

2. Timeline thinking

Trace timeline — это история запроса. Ищи:

  • Самые длинные spans (узкое место)

  • Failed spans (где падает)

  • Unexpected calls (лишние вызовы)

3. Log-trace correlation

Логи без контекста бесполезны. trace_id связывает:

  • Один лог → полный request flow

  • Structured attributes → фильтрация и поиск

4. Aggregations matter

Не смотри на один span. Aggregations показывают:

  • perMin(count()) — частота проблемы

  • p99(_dur_ms) — worst case latency

  • error_rate — % падений

5. От расследования к предотвращению

Расследование не заканчивается на исправлении. Мониторы превращают знание в автоматизацию.

Заключение

Distributed tracing — это не просто «смотреть на логи». Это системный подход к расследованию:

  1. Service graph — найти проблемный сервис

  2. Span filtering — изолировать проблемные запросы

  3. Trace timeline — понять узкое место

  4. Logs correlation — первопричина

  5. Monitors — предотвращение

С правильными инструментами (OpenTelemetry + Uptrace) путь от симптома до решения занимает меньше минуты вместо часов отладки.

Что дальше?

Вопросы? Комментарии? Пишите ниже! 👇