Трассировка с OpenTelemetry

YDB SDK инструментируют операции Query Service спанами OpenTelemetry, обеспечивая распределённую трассировку от кода приложения до каждого gRPC-вызова к YDB. Спаны экспортируются по стандартному протоколу OTLP и совместимы с Jaeger, Grafana Tempo, Zipkin и любым другим бэкендом, поддерживающим OpenTelemetry.

Создаваемые спаны

В настоящее время спаны поддерживаются для операций Query Service. Поддержка топиков и других сервисов планируется в будущих версиях SDK.

Создаются следующие спаны:

Спан Тип Описание
ydb.RunWithRetry Internal Охватывает весь цикл повторных попыток для одной операции
ydb.Try Internal Один спан на каждую попытку, включая первую; дочерние RPC-спаны прикрепляются к нему
ydb.CreateSession Client Создание сессии через gRPC CreateSession и AttachStream
ydb.ExecuteQuery Client Выполнение одного YQL-запроса
ydb.BeginTransaction Client Явный вызов начала транзакции
ydb.Commit Client Фиксация транзакции
ydb.Rollback Client Откат транзакции
ydb.Driver.Initialize Internal Первичная инициализация драйвера: обнаружение кластера и аутентификация

Типичное дерево спанов для транзакционной операции с повторной попыткой выглядит следующим образом:

ydb.RunWithRetry  (Internal)
├─ ydb.Try        (Internal)   ← 1-я попытка: ERROR
│  ├─ ydb.ExecuteQuery (Client)
│  ├─ ydb.ExecuteQuery (Client)
│  └─ ydb.Commit       (Client) ← ERROR: Transaction Lock Invalidated
└─ ydb.Try        (Internal)   ← 2-я попытка: SUCCESS, ydb.retry.backoff_ms=50
   ├─ ydb.ExecuteQuery (Client)
   ├─ ydb.ExecuteQuery (Client)
   └─ ydb.Commit       (Client)

ydb.RunWithRetry является родительским спаном для всей операции с ретраями. На каждую попытку выполнения создаётся отдельный дочерний спан ydb.Try: первый ydb.Try соответствует первой попытке, второй — первой повторной попытке и так далее. RPC-спаны конкретной попытки, например ydb.ExecuteQuery и ydb.Commit, создаются внутри соответствующего ydb.Try.

Примечание

Если попытка завершилась ошибкой, её спан ydb.Try завершается со статусом ошибки. При повторной попытке создаётся новый ydb.Try; начиная со второй попытки на нём указывается атрибут ydb.retry.backoff_ms — время ожидания перед этой попыткой в миллисекундах. Это ожидание входит в длительность следующего спана ydb.Try: спан начинается перед backoff-паузой, затем после паузы выполняются RPC-вызовы этой попытки.

Атрибуты спанов

SDK использует как стандартные атрибуты OpenTelemetry semantic conventions, так и YDB-специфичные расширения.

Стандартные атрибуты OpenTelemetry

Следующие атрибуты относятся к стабильным OpenTelemetry semantic conventions и могут обрабатываться бэкендами трассировки как стандартные:

Атрибут Где устанавливается Описание
db.system.name RPC-спаны Всегда "ydb"
db.namespace RPC-спаны Путь к базе данных YDB
server.address RPC-спаны Основной хост из строки подключения
server.port RPC-спаны Основной порт из строки подключения
network.peer.address RPC-спаны Фактический gRPC-эндпоинт, использованный для вызова
network.peer.port RPC-спаны Фактический порт gRPC-эндпоинта, использованный для вызова
error.type Спаны, завершившиеся ошибкой Тип ошибки. Например: "transport_error", "ydb_error" или полное имя класса исключения
db.response.status_code RPC-спаны при YdbException Текстовое название статуса YDB из ошибки, например ABORTED, UNAVAILABLE, OVERLOADED

YDB-специфичные атрибуты

Следующие атрибуты являются расширениями YDB поверх стандартных semantic conventions:

Атрибут Где устанавливается Описание
ydb.node.id RPC-спаны Идентификатор узла YDB, обработавшего запрос
ydb.node.dc RPC-спаны Датацентр узла YDB, обработавшего запрос
ydb.retry.backoff_ms Спаны ydb.Try, начиная со второй попытки Время ожидания перед повторной попыткой в миллисекундах

Контекст трассировки W3C

SDK автоматически прокидывает заголовок W3C traceparent в каждый исходящий gRPC-вызов. Это позволяет серверу YDB трассировать внутренние операции в рамках той же трассы — без дополнительной настройки. Подробнее о серверной трассировке — в разделе Передача внешнего trace-id в YDB.

Подключение к SDK

Установите адаптер OpenTelemetry для YDB Go SDK:

go get github.com/ydb-platform/ydb-go-sdk-otel

Настройте TracerProvider и передайте адаптер в ydb.Open:

package main

import (
    "context"
    "os"

    "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"

    "github.com/ydb-platform/ydb-go-sdk/v3"
    ydbOtel "github.com/ydb-platform/ydb-go-sdk-otel"
)

func main() {
    ctx := context.Background()

    exporter, err := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint("localhost:4317"),
        otlptracegrpc.WithInsecure(),
    )
    if err != nil {
        panic(err)
    }
    res, _ := resource.Merge(resource.Default(), resource.NewSchemaless(
        attribute.String("service.name", "my-service"),
    ))
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    defer tp.Shutdown(ctx)
    otel.SetTracerProvider(tp)

    db, err := ydb.Open(ctx,
        os.Getenv("YDB_CONNECTION_STRING"),
        ydbOtel.WithTraces(
            ydbOtel.WithTracer(tp.Tracer("ydb-go-sdk")),
        ),
    )
    if err != nil {
        panic(err)
    }
    defer db.Close(ctx)
}

Установите дополнительные зависимости opentelemetry и экспортёр OTLP:

pip install ydb[opentelemetry]
pip install opentelemetry-exporter-otlp-proto-grpc

Вызовите enable_tracing() после настройки глобального TracerProvider:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource

import ydb
from ydb.opentelemetry import enable_tracing

resource = Resource(attributes={"service.name": "my-service"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317"))
)
trace.set_tracer_provider(provider)

enable_tracing()

with ydb.Driver(endpoint="grpc://localhost:2136", database="/local") as driver:
    driver.wait(timeout=5)
    with ydb.QuerySessionPool(driver) as pool:
        pool.execute_with_retries("SELECT 1")

provider.shutdown()

Добавьте NuGet-пакет:

dotnet add package Ydb.Sdk.OpenTelemetry

Зарегистрируйте инструментацию YDB при настройке OpenTelemetry в вашем сервисе:

services.AddOpenTelemetry()
    .WithTracing(builder => builder
        .AddYdb()
        .AddOtlpExporter());

Добавьте зависимости YDB SDK и OpenTelemetry (пример для Maven):

<dependency>
    <groupId>tech.ydb</groupId>
    <artifactId>ydb-sdk-core</artifactId>
    <version>${ydb.sdk.version}</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    <version>${otel.version}</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
    <version>${otel.version}</version>
</dependency>

Создайте экземпляр OpenTelemetry SDK и передайте его в транспорт через OpenTelemetryTracer:

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import tech.ydb.core.auth.CloudAuthHelper;
import tech.ydb.core.grpc.GrpcTransport;
import tech.ydb.core.opentelemetry.OpenTelemetryTracer;
import tech.ydb.query.QueryClient;

Resource resource = Resource.getDefault().toBuilder()
    .put(ResourceAttributes.SERVICE_NAME, "my-service")
    .build();

SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
    .setResource(resource)
    .addSpanProcessor(BatchSpanProcessor.builder(
        OtlpGrpcSpanExporter.builder()
            .setEndpoint("http://localhost:4317")
            .build()
    ).build())
    .build();

OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
    .setTracerProvider(tracerProvider)
    .build();

try (GrpcTransport transport = GrpcTransport.forConnectionString(connectionString)
        .withAuthProvider(CloudAuthHelper.getAuthProviderFromEnviron())
        .withTracer(OpenTelemetryTracer.fromOpenTelemetry(openTelemetry))
        .build();
     QueryClient queryClient = QueryClient.newClient(transport).build()) {
    // Используйте queryClient здесь
}

При использовании JDBC-драйвера достаточно добавить параметр enableOpenTelemetryTracer=true в строку подключения — драйвер подхватит глобальный OTel-провайдер автоматически:

jdbc:ydb://<host>:<port>/<database>?enableOpenTelemetryTracer=true

Подключите заголовок трассировки OpenTelemetry из YDB C++ SDK и добавьте зависимость на OTel C++ SDK:

#include <ydb-cpp-sdk/client/driver/driver.h>
#include <ydb-cpp-sdk/open_telemetry/trace.h>

#include <opentelemetry/exporters/otlp/otlp_http_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_http_exporter_options.h>
#include <opentelemetry/sdk/trace/tracer_provider.h>
#include <opentelemetry/sdk/trace/simple_processor_factory.h>
#include <opentelemetry/sdk/resource/resource.h>
#include <opentelemetry/trace/provider.h>

namespace sdktrace = opentelemetry::sdk::trace;
namespace otlp     = opentelemetry::exporter::otlp;
namespace resource = opentelemetry::sdk::resource;
using namespace NYdb;

// 1. Инициализируем провайдер трассировки OTel
otlp::OtlpHttpExporterOptions opts;
opts.url = "http://localhost:4318/v1/traces";
auto exporter  = otlp::OtlpHttpExporterFactory::Create(opts);
auto processor = sdktrace::SimpleSpanProcessorFactory::Create(std::move(exporter));
auto res       = resource::Resource::Create({{"service.name", "my-service"}});
auto otelProvider = std::make_shared<sdktrace::TracerProvider>(
    std::move(processor), res);
opentelemetry::trace::Provider::SetTracerProvider(otelProvider);

// 2. Оборачиваем в провайдер трассировки YDB
auto ydbTraceProvider = NTrace::CreateOtelTraceProvider(otelProvider);

// 3. Создаём драйвер YDB с включённой трассировкой
auto driverConfig = TDriverConfig()
    .SetEndpoint("localhost:2136")
    .SetDatabase("/local")
    .SetTraceProvider(ydbTraceProvider);

TDriver driver(driverConfig);

Установите @ydbjs/telemetry вместе с OpenTelemetry Node SDK и OTLP-экспортёром:

npm install @ydbjs/telemetry @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http

Инициализируйте NodeSDK до создания драйвера и вызовите register() из @ydbjs/telemetry:

import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Driver } from '@ydbjs/core'
import { query } from '@ydbjs/query'
import { register } from '@ydbjs/telemetry'

const sdk = new NodeSDK({
    serviceName: 'my-service',
    traceExporter: new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' }),
})
sdk.start()

// Должно быть вызвано ДО создания Driver — middleware пропагации W3C
// trace context устанавливается один раз при construction'е драйвера.
const instrumentation = register()

using driver = new Driver(process.env.YDB_CONNECTION_STRING)
await driver.ready()
await using sql = query(driver)
// ...

instrumentation.disable()
await sdk.shutdown()

Альтернативно, через --import для автозагрузки до старта приложения:

node --import @opentelemetry/sdk-node/register --import @ydbjs/telemetry/register your-app.js