← Назад к вопросам

Какая минимальная единица в tracing?

1.3 Junior🔥 101 комментариев
#Observability

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Трассировка (Tracing): минимальная единица — Span

В распределённых системах трассировка (tracing) — это метод мониторинга, позволяющий отслеживать путь запроса через множество сервисов. Span — это фундаментальная, минимальная и неделимая единица трассировки. Он представляет собой отдельную, логически завершённую операцию или рабочий этап внутри одного сервиса.

Что такое Span?

Span — это запись о конкретном отрезке работы: одном вызове функции, одном HTTP-запросе, одной операции с базой данных и т.д. Каждый span содержит:

  • Метаданные: Уникальный идентификатор (SpanID), идентификатор родительского span'а (ParentSpanID), имя операции, временные метки начала и окончания.
  • Контекст: TraceID — общий для всех span'ов в пределах одного сквозного запроса (трейса), что позволяет их связать.
  • Аннотации и теги: Произвольные пары ключ-значение для добавления контекстной информации (например, http.method=GET, db.query="SELECT...", error=true).
  • Логи (logs): События с временными метками, произошедшие в течение жизни span'а (например, "получен запрос", "произошла ошибка валидации").

Пример Span в коде на Go (с использованием библиотеки OpenTelemetry)

package main

import (
    "context"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
)

func processOrder(ctx context.Context) {
    // 1. Создаём tracer. "order-service" — имя инструментируемого сервиса.
    tracer := otel.Tracer("order-service")

    // 2. Начинаем новый span. "ProcessOrder" — имя операции.
    //    Контекст `ctx` может уже содержать родительский span (если вызов вложенный).
    ctx, span := tracer.Start(ctx, "ProcessOrder")
    // 3. ВАЖНО: Не забываем завершить span в конце функции.
    defer span.End()

    // 4. Добавляем атрибуты (теги) к span.
    span.SetAttributes(
        attribute.String("order.id", "12345"),
        attribute.Int("item.count", 2),
    )

    // 5. Имитируем полезную работу.
    time.Sleep(10 * time.Millisecond)

    // 6. Записываем событие (log) в span.
    span.AddEvent("Order validated successfully")

    // 7. Выполняем вложенную операцию, создавая child span.
    checkInventory(ctx)
}

func checkInventory(ctx context.Context) {
    // Этот span будет дочерним по отношению к "ProcessOrder".
    tracer := otel.Tracer("order-service")
    _, span := tracer.Start(ctx, "CheckInventory")
    defer span.End()

    span.SetAttributes(attribute.String("db.operation", "query"))
    time.Sleep(5 * time.Millisecond)
}

Иерархия: из Spans состоит Trace

Spans организуются в древовидную структуру, которая и формирует полную трассировку (trace):

  • Trace (Трассировка): Полная история одного сквозного запроса (например, "Покупка товара"). Идентифицируется уникальным TraceID.
  • Root Span (Корневой Span): Первый span в трейсе, не имеющий родителя. Обычно создаётся на входе в систему (например, в HTTP-обработчике).
  • Child Span (Дочерний Span): Span, созданный в контексте другого (родительского) span'а. Отношения задаются через ParentSpanID. Это позволяет отображать вложенность операций: вызов другого сервиса, запрос к БД внутри бизнес-логики.

Пример иерархии трейса:

Trace (TraceID: abc123)
│
├── [Span: "HTTP POST /order"] (SpanID: 1, ParentSpanID: nil) [Длительность: 50мс]
│   ├── [Span: "ProcessOrder"] (SpanID: 2, ParentSpanID: 1) [Длительность: 30мс]
│   │   ├── [Span: "CheckInventory"] (SpanID: 3, ParentSpanID: 2) [Длительность: 5мс]
│   │   └── [Span: "ChargePayment"] (SpanID: 4, ParentSpanID: 2) [Длительность: 15мс]
│   └── [Span: "Save to DB"] (SpanID: 5, ParentSpanID: 1) [Длительность: 10мс]
└── [Span: "Send confirmation email"] (SpanID: 6, ParentSpanID: 1) [Длительность: 100мс] (асинхронно)

Почему именно Span — это минимальная единица?

  1. Неделимость: Span — это атомарная запись о работе. Более мелких стандартизированных элементов в модели данных трассировки не существует.
  2. Измеримость: Он имеет четко определённое начало и конец, что позволяет измерять латентность (latency) конкретной операции.
  3. Контекстность: Наличие TraceID и ParentSpanID делает span не просто изолированным событием, а частью большего целого — трейса.
  4. Стандартизация: Такая модель (span в составе trace) является краеугольным камнем современных стандартов трассировки, таких как OpenTelemetry, Jaeger и Zipkin.

Практическое значение для разработчика на Go

Понимание span'а критично для эффективной инструментации кода:

  • Инструментирование (Instrumentation): Вы встраиваете в код создание и завершение span'ов вокруг значимых блоков логики.
  • Анализ проблем: В сложных микросервисных архитектурах span'ы позволяют точно определить, в каком сервисе и на каком этапе произошла задержка или ошибка. Вы видите не просто "запрос медленный", а "запрос медленный, потому что span 'CallUserService' длился 3000 мс".
  • Контекстное распространение: В Go это особенно важно, так как контекст (context.Context) является стандартным способом передачи TraceID и активного SpanID между горутинами и вызовами функций, обеспечивая корректное построение дерева трейса.

Итог: Span — это элементарный "кирпичик" наблюдения за поведением системы. Трассировка собирает эти кирпичики в целостную картину (trace), позволяя инженерам понимать, анализировать и отлаживать работу распределённых приложений на качественно новом уровне.

Какая минимальная единица в tracing? | PrepBro