> Используете ли трейсинг, например Jaeger (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Wildberries

Стек: Go

> Пример ответа

Короткий ответ

Да, использую трейсинг в production-системах на Go, в основном с Jaeger или OpenTelemetry Collector как агентом. Трейсинг критичен для распределённых систем: он даёт сквозную видимость запроса через сервисы, базы данных и очереди. В Go обычно подключаю OpenTelemetry SDK, экспортирую спаны в Jaeger, и настраиваю sampling (обычно head-based с rate limiting). Это помогает быстро находить узкие места и ошибки в цепочках вызовов.

Подробное объяснение

Трейсинг - это инструмент observability, который фиксирует путь запроса через распределённую систему. В отличие от логов и метрик, трейсинг показывает временную иерархию операций: каждый span - это единица работы с именем, временем начала, длительностью и атрибутами. Спаны связаны в trace через trace ID и parent-child отношения.

В Go стандартный подход - OpenTelemetry SDK (бывший OpenTracing). Основные компоненты:

  • TracerProvider - фабрика трасеров, конфигурируется один раз на процесс.
  • Tracer - создаёт спаны, обычно через tracer.Start(ctx, "operation").
  • Propagation - передача trace context через HTTP headers (например, traceparent), gRPC metadata или Kafka headers.
  • Exporter - отправка спанов в бэкенд (Jaeger, Zipkin, Tempo, Datadog).

Для Jaeger есть два пути: использовать OTLP exporter в OpenTelemetry Collector, который затем форвардит в Jaeger, или напрямую использовать Jaeger exporter. Первый вариант предпочтительнее, потому что Collector позволяет агрегировать, фильтровать и батчить спаны, а также менять бэкенд без изменения кода.

Ключевые практики:

  • Sampling: не трейсить каждый запрос. Head-based sampling (решение до выполнения) с rate 5-10% для высоконагруженных систем, или tail-based sampling для редких ошибок.
  • Context propagation: всегда передавать context.Context через все функции, даже если он не используется напрямую - это обязательное условие для корректного трейсинга.
  • Span attributes: добавлять бизнес-атрибуты (user ID, order ID, error code) - без них трейс бесполезен для диагностики.
  • Naming: спаны называть по шаблону HTTP GET /api/v1/users, DB SELECT users, Kafka consume topic.

На практике

В Go-сервисах я обычно делаю так:

  1. В main.go создаю TracerProvider с OTLP exporter, настраиваю resource (service.name, version, environment).
  2. Добавляю middleware для HTTP/gRPC, который стартует span на каждый входящий запрос, записывает метод, path, status code, длительность.
  3. Для исходящих вызовов (HTTP клиент, gRPC, SQL, Kafka) использую instrumentation libraries: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp, go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc, go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql.
  4. Для ручных операций (например, парсинг файла, вызов внешнего API без готовой instrumentation) создаю дочерние спаны через tracer.Start(ctx, "operation").
  5. Настраиваю sampling: ParentBased(AlwaysSample) для development, TraceIdRatioBased(0.1) для production.

Важно: трейсинг не заменяет логи и метрики. Он отвечает на вопрос "где именно запрос потратил время", но не на "сколько ошибок было в час". Поэтому в системах всегда три кита: logs, metrics, traces.

Пример кода

GO
package main
import (
"context"
"log"
"net/http"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func initTracer() func() {
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("jaeger:4317"),
otlptracegrpc.WithInsecure(),
)
if err != nil {
log.Fatal(err)
}
res, _ := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("payment-service"),
semconv.ServiceVersion("1.0.0"),
),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)),
)
otel.SetTracerProvider(tp)
return func() { _ = tp.Shutdown(ctx) }
}
func main() {
shutdown := initTracer()
defer shutdown()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ручной span для бизнес-операции
tracer := otel.Tracer("payment")
ctx, span := tracer.Start(r.Context(), "process.payment")
defer span.End()
span.SetAttributes(
attribute.String("payment.id", "12345"),
attribute.Int("amount", 100),
)
// имитация работы
time.Sleep(50 * time.Millisecond)
w.WriteHeader(http.StatusOK)
})
// middleware для автоматического трейсинга входящих запросов
wrapped := otelhttp.NewHandler(handler, "HTTP POST /payment")
log.Fatal(http.ListenAndServe(":8080", wrapped))
}

Как отвечать на собеседовании

Начни с конкретного примера из практики: "Да, в последнем проекте мы использовали Jaeger через OpenTelemetry Collector". Расскажи, какую проблему решал трейсинг (например, медленные ответы из-за N+1 запросов в БД или потерянные сообщения в Kafka). Покажи понимание propagation - это главный вопрос, который любят задавать. Упомяни sampling и почему нельзя трейсить всё подряд. Если спросят про альтернативы - скажи про Zipkin, Tempo, Datadog, но подчеркни, что OpenTelemetry - стандарт де-факто.

Не углубляйся в детали конфигурации Jaeger UI, лучше расскажи про архитектуру: SDK → Collector → Jaeger. Если спросят про производительность - скажи, что overhead обычно < 1-2% при разумном sampling, и что можно использовать async exporter.

Что проверяет интервьюер

  • Понимание разницы между tracing, logging и metrics.
  • Знание механизма propagation (trace ID, parent span ID, headers).
  • Умение объяснить, как трейсинг помогает в дебаге распределённых систем.
  • Практический опыт: как настраивал, какие проблемы решал, как боролся с overhead.
  • Знание экосистемы Go: OpenTelemetry SDK, instrumentation libraries, middleware.
  • Понимание sampling стратегий и trade-off между полнотой данных и стоимостью.

Типичные ошибки

  • Путать трейсинг с логированием: "мы просто пишем request ID в логи" - это не трейсинг.
  • Не упоминать propagation - без него трейсинг в распределённой системе бесполезен.
  • Говорить, что трейсинг решает все проблемы observability - это не так, нужны метрики и логи.
  • Не знать, как передать context в Go: context.Context должен быть первым аргументом функции.
  • Предлагать трейсить каждый запрос без sampling - это приведёт к перегрузке хранилища и сети.
  • Забывать про resource attributes (service.name) - без них нельзя фильтровать трейсы по сервисам.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью