Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ project(YDB-CPP-SDK VERSION ${YDB_SDK_VERSION} LANGUAGES C CXX ASM)
option(YDB_SDK_INSTALL "Install YDB C++ SDK" Off)
option(YDB_SDK_TESTS "Build YDB C++ SDK tests" Off)
option(YDB_SDK_EXAMPLES "Build YDB C++ SDK examples" On)
option(YDB_SDK_ENABLE_OTEL_METRICS "Build OpenTelemetry metrics plugin" Off)
option(YDB_SDK_ENABLE_OTEL_TRACE "Build OpenTelemetry trace plugin" Off)
set(YDB_SDK_GOOGLE_COMMON_PROTOS_TARGET "" CACHE STRING "Name of cmake target preparing google common proto library")
option(YDB_SDK_USE_RAPID_JSON "Search for rapid json library in system" ON)

Expand Down Expand Up @@ -58,6 +60,7 @@ add_subdirectory(library/cpp)
add_subdirectory(include/ydb-cpp-sdk/client)
add_subdirectory(src)
add_subdirectory(util)
add_subdirectory(plugins)

#_ydb_sdk_validate_public_headers()

Expand Down
5 changes: 5 additions & 0 deletions cmake/external_libs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ find_package(Brotli 1.1.0 REQUIRED)
find_package(jwt-cpp REQUIRED)
find_package(double-conversion REQUIRED)

# OpenTelemetry
if (YDB_SDK_ENABLE_OTEL_METRICS OR YDB_SDK_ENABLE_OTEL_TRACE)
find_package(opentelemetry-cpp REQUIRED)
endif()

# RapidJSON
if (YDB_SDK_USE_RAPID_JSON)
find_package(RapidJSON REQUIRED)
Expand Down
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ add_subdirectory(topic_writer/transaction)
add_subdirectory(ttl)
add_subdirectory(vector_index)
add_subdirectory(vector_index_builtin)

if (YDB_SDK_ENABLE_OTEL_TRACE AND YDB_SDK_ENABLE_OTEL_METRICS)
add_subdirectory(otel_tracing)
endif()
41 changes: 41 additions & 0 deletions examples/otel_tracing/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
add_executable(otel_tracing_example)

target_link_libraries(otel_tracing_example PUBLIC
yutil
getopt
YDB-CPP-SDK::Query
YDB-CPP-SDK::Table
YDB-CPP-SDK::Params
YDB-CPP-SDK::Driver
YDB-CPP-SDK::OpenTelemetryTrace
YDB-CPP-SDK::OpenTelemetryMetrics
opentelemetry-cpp::otlp_http_exporter
opentelemetry-cpp::otlp_http_metric_exporter
)

target_sources(otel_tracing_example PRIVATE
main.cpp
)

vcs_info(otel_tracing_example)

if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
target_link_libraries(otel_tracing_example PUBLIC
cpuid_check
)
endif()

if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(otel_tracing_example PRIVATE
-ldl
-lrt
-Wl,--no-as-needed
-lpthread
)
elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_link_options(otel_tracing_example PRIVATE
-Wl,-platform_version,macos,11.0,11.0
-framework
CoreFoundation
)
endif()
186 changes: 186 additions & 0 deletions examples/otel_tracing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# YDB C++ SDK — OpenTelemetry Demo

Демонстрация трассировки и метрик операций QueryService и TableService
с визуализацией в **Grafana**, **Jaeger** и **Prometheus**.

## Архитектура

```
┌──────────────┐ OTLP/HTTP ┌──────────────────┐
│ C++ demo │ ──────────────────> │ OTel Collector │
│ application │ │ :4328 (HTTP) │
└──────────────┘ └────────┬──────────┘
│ │
traces │ │ metrics
▼ ▼
┌──────────┐ ┌────────────┐
│ Jaeger │ │ Prometheus │
│ :16686 │ │ :9090 │
└─────┬─────┘ └──────┬──────┘
│ │
└───────┬───────┘
┌──────────┐
│ Grafana │
│ :3000 │
└──────────┘
```

## Быстрый старт

### 1. Запустить инфраструктуру

```bash
cd examples/otel_tracing
docker compose up -d
```

Дождитесь готовности YDB:

```bash
docker compose logs ydb -f
# Ждите строку "Database started successfully"
```

### 2. Собрать SDK с OTel и тестами

Из корня репозитория:

```bash
mkdir -p build && cd build

cmake .. \
-DYDB_SDK_TESTS=ON \
-DYDB_SDK_ENABLE_OTEL_TRACE=ON \
-DYDB_SDK_ENABLE_OTEL_METRICS=ON

cmake --build . --target otel_tracing_example -j$(nproc)
```

### 3. Запустить демо

```bash
./examples/otel_tracing/otel_tracing_example \
--endpoint localhost:2136 \
--database /local \
--otlp http://localhost:4328 \
--iterations 20 \
--retry-workers 6 \
--retry-ops 30
```

#### Доступные флаги

| Флаг | По умолчанию | Описание |
|--------------------|---------------------------|--------------------------------------------------------------------------|
| `--endpoint`, `-e` | `localhost:2136` | gRPC-эндпоинт YDB |
| `--database`, `-d` | `/local` | Имя базы |
| `--otlp` | `http://localhost:4328` | OTLP/HTTP endpoint коллектора |
| `--iterations`,`-n`| `20` | Итераций в Query- и Table-нагрузке |
| `--retry-workers` | `6` | Параллельных воркеров в retry-нагрузке (`0` чтобы пропустить) |
| `--retry-ops` | `30` | Операций на каждого retry-воркера |

#### Демонстрация реальных ретраев

Третий встроенный сценарий — `RunRetryWorkload` — намеренно провоцирует
**SERIALIZABLE-конфликты**: N параллельных воркеров делают
`SELECT → sleep → UPSERT → COMMIT` на одной и той же «горячей» строке
(`id = 9999`) внутри `RetryQuerySync`. YDB возвращает `ABORTED`
проигравшим транзакциям, и SDK прозрачно ретраит их.

В трейсах появятся:

```
ydb.RunWithRetry (INTERNAL)
├── ydb.Try attempt=0 (INTERNAL, backoff_ms=0)
│ ├── ydb.CreateSession
│ ├── ydb.ExecuteQuery
│ └── ydb.Commit status=ABORTED, error.type=ABORTED, exception event
├── ydb.Try attempt=1 (INTERNAL, backoff_ms=...)
│ └── ... status=ABORTED
└── ydb.Try attempt=N (INTERNAL)
└── ... status=SUCCESS
```

Для усиления конфликтов поднимите воркеров и операций:

```bash
./examples/otel_tracing/otel_tracing_example \
--retry-workers 12 --retry-ops 80
```

В конце программа печатает счётчик наблюдённых абортов — каждый из них
соответствует одному автоматическому ретраю SDK.

> **Важно:** для статуса `ABORTED` SDK использует политику
> `RetryImmediately` (см. `src/client/impl/internal/retry/retry.h`),
> поэтому атрибут `ydb.retry.backoff_ms` будет равен `0` —
> это by design. Чтобы увидеть `backoff_ms > 0`, нужны статусы
> `UNAVAILABLE` (FastBackoff, slot 5 ms) или `OVERLOADED` /
> `CLIENT_RESOURCE_EXHAUSTED` (SlowBackoff, slot 1 s). Самый простой способ
> их получить — кратковременно перезапустить YDB во время работы примера:
>
> ```bash
> ./examples/otel_tracing/otel_tracing_example --retry-workers 8 --retry-ops 100 &
> sleep 5
> docker compose -f examples/otel_tracing/docker-compose.yml restart ydb
> wait
> ```

### 4. Открыть дашборды

| Сервис | URL | Описание |
|-----------|------------------------------|---------------------------------|
| Grafana | http://localhost:3000 | Дашборд "YDB QueryService" |
| Jaeger | http://localhost:16686 | Поиск трейсов по сервису |
| Prometheus| http://localhost:9090 | Метрики `db_client_operation_*` |

**Grafana**: логин `admin` / пароль `admin`.

### 5. Что смотреть

#### В Grafana (дашборд "YDB QueryService"):
- **Request Rate by Operation** — RPS по операциям (ExecuteQuery, ExecuteDataQuery, CreateSession, Commit, Rollback)
- **Error Rate by Operation** — частота ошибок
- **Duration p50/p95/p99** — распределение длительности операций
- **Error Ratio** — процент ошибок
- **Recent Traces** — таблица трейсов из Jaeger

#### В Jaeger UI:
- Выберите сервис `ydb-cpp-sdk-demo`.
- RPC-спаны (`SpanKind = CLIENT`):
`ydb.CreateSession`, `ydb.ExecuteQuery`, `ydb.ExecuteDataQuery`,
`ydb.BeginTransaction`, `ydb.Commit`, `ydb.Rollback`,
`ydb.ExecuteSchemeQuery`, `ydb.BulkUpsert`.
- Retry-спаны (`SpanKind = INTERNAL`): `ydb.RunWithRetry`,
`ydb.Try` (по одному на каждую попытку, с атрибутами
`ydb.retry.attempt`, `ydb.retry.backoff_ms`).
- Общие атрибуты на всех YDB-спанах:
- `db.system.name = ydb`
- `db.namespace` (имя базы)
- `server.address`, `server.port` (эндпоинт балансера)
- `network.peer.address`, `network.peer.port` (фактический узел кластера)
- На ошибках добавляются:
- `db.response.status_code` — строковый статус YDB (например, `ABORTED`)
- `error.type` — тот же строковый статус
- событие `exception` с `exception.type` и `exception.message`

#### В Prometheus:
- `db_client_operation_duration_seconds_bucket` — гистограмма длительности
(OTel Semantic Conventions). Лейблы: `db.system.name`, `db.namespace`,
`db.operation.name` (с префиксом `ydb.`), `ydb.client.api`
(`Query` / `Table`). Для ошибок добавляются `db.response.status_code`
и `error.type`.
- `db_client_operation_requests_total` — счётчик начатых операций
(включая каждую попытку ретрая).
- `db_client_operation_errors_total` — счётчик неуспешных попыток.
Полезно сравнивать с `requests_total`: для retry-нагрузки на той же
«горячей» строке коэффициент ошибок будет очень высоким — это и есть
индикатор работы ретраев.

### 6. Остановить

```bash
cd examples/otel_tracing
docker compose down -v
```
70 changes: 70 additions & 0 deletions examples/otel_tracing/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
services:
ydb:
image: cr.yandex/yc/yandex-docker-local-ydb:latest
platform: linux/amd64
ports:
- "2136:2136"
- "8765:8765"
environment:
- GRPC_TLS_PORT=2135
- GRPC_PORT=2136
- MON_PORT=8765
- YDB_DEFAULT_LOG_LEVEL=NOTICE
- YDB_USE_IN_MEMORY_PDISKS=true
volumes:
- ydb-data:/ydb_data
healthcheck:
test: /bin/sh -c "/ydb -e grpc://localhost:2136 -d /local scheme ls"
interval: 5s
timeout: 5s
retries: 20

jaeger:
image: jaegertracing/all-in-one:1.76.0
ports:
- "16686:16686"
- "4317:4317"
- "4318:4318"
environment:
- COLLECTOR_OTLP_ENABLED=true

prometheus:
image: prom/prometheus:v2.53.0
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
depends_on:
- otel-collector

otel-collector:
image: otel/opentelemetry-collector-contrib:latest
ports:
- "4327:4317"
- "4328:4318"
- "8889:8889"
volumes:
- ./otel-collector/config.yml:/etc/otelcol-contrib/config.yaml:ro
depends_on:
- jaeger

grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana-data:/var/lib/grafana
depends_on:
- jaeger
- prometheus

volumes:
ydb-data:
grafana-data:
Loading
Loading