Що таке distributed tracing
У мікросервісній архітектурі одна дія в мобільному застосунку запускає каскад із 10–20 міжсервісних звернень. Якщо запит гальмує або завершується помилкою:
🟢 Junior Level
Distributed Tracing (розподілене трасування) — це метод діагностики та моніторингу, який дозволяє відстежити повний шлях проходження клієнтського запиту крізь увесь ланцюжок мікросервісів, баз даних і черг повідомлень.
Користувач: POST /checkout
│
▼
[API Gateway] ─────────────── (Загальний час: 240 мс) ──────────────┐
│ │
├──► [Order Service] ────── (180 мс) ─────────────────────────┐ │
│ │ │ │
│ ├──► [Payment Service] ─── (120 мс: ВУЗЬКЕ МІСЦЕ! 🐢) ──┤ │
│ │ │ │
│ └──► [Inventory Service] ─ (30 мс) ────────────────────┘ │
│ │
└──► [Notification Service] (Асинхронно через Kafka: 15 мс) ──────┘
Навіщо це потрібно?
У мікросервісній архітектурі одна дія в мобільному застосунку запускає каскад із 10–20 міжсервісних звернень. Якщо запит гальмує або завершується помилкою:
- Звичайні ізольовані логи безпорадні: вони розпорошені по десятках серверів, і неможливо зрозуміти, який саме рядок логу в сервісі платежів стосується конкретного запиту користувача.
- Distributed Tracing надає точний таймлайн (водоспад затримок / Waterfall Chart): який саме мікросервіс спричинив лаг, на якому рядку коду чи SQL-запиті виникла затримка та де саме впав виняток.
🟡 Middle Level
1. Анатомія трасування: Trace vs Span
Модель даних базується на класичній праці Google Dapper (2010):
Trace (Дерево викликів, Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736)
├── Span A: HTTP POST /checkout (Root Span, Gateway)
│ ├── Span B: OrderService.createOrder()
│ │ ├── Span C: SQL INSERT INTO orders
│ │ └── Span D: HTTP POST /charge (PaymentService)
│ │ └── Span E: External Bank API Call
│ └── Span F: Kafka.send("order-events")
- Trace (Трейс): Спрямований ациклічний граф (DAG) спанів, що репрезентує весь наскрізний шлях транзакції від клієнта до фінальної відповіді. Має глобальний унікальний Trace ID.
- Span (Спан): Одиничний відрізок безперервної роботи в межах одного сервісу чи операції. Містить:
- Назву операції (наприклад,
SELECT * FROM users). - Часові мітки початку та завершення (
Start Time,End Time$\to$ обчислюєтьсяDuration). - Посилання на батьківський спан (Parent Span ID).
- Attributes (Теги): Метадані (
http.status_code=200,db.statement="SELECT..."). - Events (Події): Точкові часові мітки логів усередині спана.
- Status:
OKабоERROR(із фіксацією стеку винятку).
- Назву операції (наприклад,
2. Стандарт поширення контексту: W3C TraceContext
Щоб гетерогенні сервіси розуміли, що вони обробляють один спільний потік, контекст передається через стандартизований HTTP-заголовок traceparent:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
версія Trace ID Parent Span ID прапорці (01 = sampled)
3. Реалізація у Spring Boot 3 (Micrometer Tracing + OpenTelemetry)
Важливо: Проєкт
Spring Cloud Sleuthповністю застарів і виведений з експлуатації у Spring Boot 3+. Сучасний стандарт — Micrometer Tracing із рушієм OpenTelemetry (OTel) або Brave:
<!-- Залежності для Spring Boot 3 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
Програмне створення користувацьких спанів:
@Service
@RequiredArgsConstructor
public class PaymentProcessingService {
private final Tracer tracer; // io.micrometer.tracing.Tracer
public PaymentResult processPayment(PaymentOrder order) {
// Створюємо дочірній спан для вимірювання критичної бізнес-операції
Span newSpan = tracer.nextSpan().name("fraud-check-and-charge").start();
try (Tracer.SpanInScope ws = tracer.withSpan(newSpan.start())) {
newSpan.tag("order.id", order.id().toString());
newSpan.tag("order.amount", order.amount().toPlainString());
return executeBankingTransaction(order);
} catch (Exception ex) {
newSpan.error(ex); // Фіксуємо виняток безпосередньо у спані
throw ex;
} finally {
newSpan.end(); // Завершуємо вимірювання часу
}
}
}
🔴 Senior Level
1. Стратегії семплювання: Head-Based vs Tail-Based Sampling
У системі з трафіком 100 000 RPS зберігати 100% трейсів на диск фізично неможливо й економічно невиправдано (це вимагатиме петабайти сховища та перевантажить мережу):
Head-Based Sampling (Семплювання на вході):
[Клієнт] ──► [API Gateway] ──(Кидок монети: 1% трафіку)──► Трасується
(Мінус: Рідкісна помилка 500 з імовірністю 99% буде втрачена!)
Tail-Based Sampling (Семплювання на виході через OTel Collector):
[Мікросервіси] ──(Шлють 100% спанів у буфер)──► [OpenTelemetry Collector]
│
├── Помилка 500? ──────► ЗБЕРЕГТИ В БД! ✅
├── Latency > 2 сек? ─► ЗБЕРЕГТИ В БД! ✅
└── Звичайний запит ──► Відкинути 99%
- Head-Based Sampling: Рішення ухвалюється на самому початку запиту на шлюзі (наприклад, випадково відбирати 1% викликів). Недолік: Якщо 1 із 10 000 запитів завершується аварією, семплювання з високою ймовірністю проігнорує саме збійний запит.
- Tail-Based Sampling (Сучасний Enterprise-стандарт): Сервіси передають спани в буфер OpenTelemetry Collector. Колектор накопичує трейс повністю та оцінює результат: якщо у трейсі була хоч одна помилка
ERRORабо час виконання перевищив поріг SLA — трейс зберігається в базу даних зі 100% гарантією.
2. Втрата Trace Context в асинхронному коді (MDC & Reactive Pitfall)
Ідентифікатори traceId та spanId автоматично підставляються в логи через MDC (Mapped Diagnostic Context), який під капотом використовує ThreadLocal.
Коли контекст миттєво губиться:
- При переході в
CompletableFuture.supplyAsync()або кастомнийExecutorService. - У реактивному стеку Spring WebFlux / Project Reactor, де виконання одного HTTP-запиту постійно мігрує між різними потоками Netty Event Loop.
- В асинхронних методах з анотацією
@Async.
Рішення у Spring Boot 3: Використання модуля Micrometer Context Propagation:
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
// TaskDecorator копіює контекст трасування з батьківського потоку в дочірній
executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
executor.initialize();
return executor;
}
}
А для Project Reactor вмикається глобальний хук у момент старту застосунку:
Hooks.enableAutomaticContextPropagation();
4 Tricky Questions
1. Як влаштований заголовок W3C TraceContext traceparent і чому версія зафіксована як 00?
Відповідь:
Заголовок має суворий формат із 4 полів, розділених дефісами: version-trace_id-parent_id-trace_flags:
version(2 hex-символи): Поточна версія стандарту дорівнює00. Значенняffзарезервовано під майбутні ревізії.trace_id(32 hex-символи / 16 байт): Глобальний унікальний ідентифікатор усього трейсу (генерується на вході в систему). Не може складатися лише з нулів.parent_id(16 hex-символів / 8 байт): ID вхідного батьківського спана.trace_flags(2 hex-символи / 8 біт): Бітова маска. Молодший біт01означаєSampled(трейс підлягає обов’язковому збереженню у сховищі). Стандарт гарантує, що навіть коли сервіси написані різними мовами (Java, Go, Rust, Python), контекст не загубиться під час наскрізних викликів.
2. Чим Tail-Based Sampling в OpenTelemetry Collector принципово кращий за семплювання на рівні Java-застосунку?
Відповідь: Java-застосунок на момент старту запиту (Head) не знає, чи завершиться цей запит винятком через 2 секунди у третьому сервісі. Якщо застосунок налаштований семплювати 5% трафіку, він з імовірністю 95% проґавить рідкісну унікальну аварію. Tail-Based Sampling переносить рішення на рівень зовнішнього проміжного агента (OpenTelemetry Collector):
- Усі мікросервіси транслюють 100% спанів через швидкий протокол gRPC (OTLP) на локальний колектор.
- Колектор утримує спани в кільцевому буфері пам’яті до фіналу всього трейсу.
- Якщо статус трейсу
ERRORабо затримка перевищила поріг SLA, колектор спрямовує його до сховища (Jaeger / Tempo / ClickHouse). Успішні штатні трейси агресивно відкидаються.
3. Які реальні накладні витрати (Overhead) від увімкнення Distributed Tracing у Highload-системах?
Відповідь:
- CPU Overhead: Генерація 128-бітних ID, виклики системного часу (
System.currentTimeMillis()/clock_gettime), серіалізація спанів у Protobuf. - Пам’ять (Heap Allocation): Створення об’єктів Span, Scope та Map з атрибутами створює додатковий тиск на Garbage Collector.
- Мережевий трафік: Якщо довгий трейс містить 50 спанів, обсяг телеметрії може перевищити розмір корисних бізнес-даних.
Мінімізація: Використання Protobuf/gRPC експортера (OTLP), пакетована відправка спанів (
BatchSpanProcessor) в окремому фоновому потоці та адаптивне семплювання.
4. У чому різниця між Tracing Span Tag (Attribute) та Span Log (Event)?
Відповідь:
- Tag / Attribute: Статична властивість, що описує весь спан у цілому (наприклад,
http.method = "POST",customer.tier = "VIP"). Атрибути використовуються для фільтрації та пошуку трейсів в UI (знайти всі трейси зhttp.status_code = 500). - Log / Event: Точкова подія, прив’язана до конкретної мілісекунди всередині спана (містить timestamp та опис). Наприклад: «00:00.120: Надіслано TCP-пакет», «00:00.340: Отримано перший байт відповіді». Застосовується для детального покрокового аналізу тривалих операцій.
🎯 Шпаргалка для інтерв’ю
Головне:
- Distributed Tracing відстежує наскрізний шлях клієнтського запиту крізь граф мікросервісів.
- Trace — повний шлях запиту (DAG спанів); Span — одиничний відрізок роботи всередині сервісу із заміром часу.
- Стандарт передачі контексту — W3C TraceContext (заголовок
traceparent). - Галузевий стандарт — OpenTelemetry (OTel), сховища та UI — Jaeger, Grafana Tempo, SigNoz.
- У Spring Boot 3 замість застарілого Spring Cloud Sleuth використовується Micrometer Tracing.
- Log Correlation: Трасування пов’язується з логами через автоматичну підстановку
traceIdтаspanIdв MDC Logback/SLF4J. - Під високим навантаженням використовується Tail-Based Sampling (збереження 100% помилок і повільних трейсів через OTel Collector).
Червоні прапорці (Чого говорити НЕ МОЖНА):
- ❌ «Spring Cloud Sleuth — сучасний інструмент для Spring Boot 3» (Sleuth закритий, у Spring Boot 3 використовується Micrometer Tracing).
- ❌ «У продакшені ми зберігаємо 100% трейсів при 50 000 RPS» (Це переповнить мережу та диски сховища телеметрії; семплювання є обов’язковим).
- ❌ «Трасування повністю замінює собою логування» (Трейси показують граф і затримки, а логи розкривають детальний контекст).