Делюсь практическим опытом внедрения Uptrace v2.0 — от разворачивания до оптимизации запросов. С цифрами, кодом и реальными кейсами.

TL;DR

Uptrace v2.0 использует новый JSON-тип ClickHouse для хранения трейсов, что даёт 10x ускорение запросов. Показываю на практике: установка за 5 минут, настройка трансформаций данных, экономия на retention policies. Бенчмарки на 500M span'ах: запросы стали выполняться за 0.3-0.5 сек вместо 4-7 сек.

Почему я вообще это затеял

Наша микросервисная архитектура разрослась до 30+ сервисов, и классическое логирование перестало работать. Когда пользователь жаловался на ошибку, я тратил часы на поиск: где именно упал запрос, в каком сервисе, с какими параметрами.

Сначала попробовал связку Jaeger + Grafana Loki. Работает, но:

  • Три отдельных инструмента (traces, logs, metrics)

  • Запросы по атрибутам трейсов тормозили на больших объёмах

  • Конфигурация — отдельная боль

Когда увидел анонс Uptrace v2.0 с заявленным 10x ускорением благодаря новому JSON-типу ClickHouse, решил попробовать. Спойлер: оно того стоило.

В чём была проблема (технически)

Observability-системы работают с вложенными JSON-структурами. Каждый span или лог — это объект с непредсказуемым набором атрибутов:

{
  "trace_id": "abc123",
  "service_name": "checkout",
  "http.method": "POST",
  "http.target": "/api/v1/orders/12345",
  "user_id": "user_987",
  "error": true,
  "db.statement": "SELECT * FROM orders WHERE id = ?"
}

Классические подходы к хранению:

Вариант 1: Flatten всё в колонки

CREATE TABLE spans (
  trace_id String,
  service_name String,
  http_method String,
  http_target String,
  user_id String,
  -- ... ещё 100500 колонок для всех возможных атрибутов
)

Проблемы:

  • ❌ Схема раздувается

  • ❌ Теряется гибкость

  • ❌ Нужен ETL для каждого нового атрибута

Вариант 2: Хранить как текст + JSONExtractString

CREATE TABLE spans (
  trace_id String,
  attributes String  -- просто JSON-строка
)

-- Запрос:
SELECT count()
FROM spans
WHERE JSONExtractString(attributes, 'service_name') = 'checkout';

Проблемы:

  • Медленно: парсинг JSON на каждый запрос

  • ❌ Нет индексации по вложенным полям

  • ❌ На 50M записей такой запрос ж��ал 2.7 секунды

Что изменилось в Uptrace v2.0

ClickHouse добавил нативный тип JSON, и Uptrace v2.0 полностью на него перешёл. Теперь:

До (Uptrace v1.x):

SELECT count()
FROM spans_old
WHERE JSONExtractString(attributes, 'service_name') = 'checkout';

-- Результат: 2.754 секунды на 50 млн записей
-- Processed: 50.00 million rows, 8.43 GB

После (Uptrace v2.0):

SELECT count()
FROM spans_new
WHERE attributes.service_name = 'checkout';

-- Результат: 0.287 секунды на 50 млн записей ⚡
-- Processed: 50.00 million rows, 3.12 GB

10x ускорение! И это не магия — JSON парсится один раз при вставке, а затем хранится колоночно. Точечная нотация attributes.service_name работает как обычная колонка.

Практика: разворачиваем Uptrace v2.0

Шаг 1: Установка (Docker — самый быстрый способ)

# Клонируем репозиторий
git clone https://github.com/uptrace/uptrace
cd uptrace/example/docker

# Запускаем всё одной командой
docker-compose up -d

Что внутри docker-compose.yml:

  • ClickHouse — хранилище данных

  • Uptrace — сам интерфейс и API

  • PostgreSQL — метаданные (проекты, пользователи, дашборды)

Через минуту открываем http://localhost:14318:

  • Логин: admin@uptrace.local

  • Пароль: admin

Шаг 2: Создаём проект через UI

В v2.0 появилось управление через интерфейс (раньше только конфиги!). Идём в Organization → New Org → New Project:

Создаем проект
Создаем проект

Указываем:

  • Name: production

  • Organization: My Company

  • Token генерируется автоматически

Альтернативно через конфиг uptrace.yml:

seed_data:
  users:
    - key: admin_user
      name: Admin
      email: admin@example.com
      password: changeme  # Обязательно меняем!
  
  orgs:
    - key: my_org
      name: My Company
  
  org_users:
    - key: org_admin
      org: my_org
      user: admin_user
      role: owner
  
  projects:
    - key: prod_project
      name: production
      org: my_org

Ключевая фишка: поле key позволяет Uptrace автоматически обновлять ресурсы при изменении конфига. Раньше приходилось пересоздавать вручную.

Шаг 3: Отправляем первые трейсы

Uptrace поддерживает OpenTelemetry из коробки. Пример на Node.js:

const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

// Создаём exporter для Uptrace
const exporter = new OTLPTraceExporter({
  url: 'http://localhost:14318/v1/traces',
  headers: {
    'uptrace-dsn': 'http://project_token@localhost:14318/1'
  }
});

// Настраиваем провайдер
const provider = new NodeTracerProvider({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'my-service',
    [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
  })
});

provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();

// Используем в коде
const tracer = provider.getTracer('my-service');

async function handleRequest(req, res) {
  const span = tracer.startSpan('handleRequest', {
    attributes: {
      'http.method': req.method,
      'http.target': req.url,
      'user_id': req.user?.id
    }
  });

  try {
    await processOrder(req.body);
    span.setStatus({ code: 1 }); // OK
  } catch (error) {
    span.recordException(error);
    span.setStatus({ code: 2, message: error.message }); // ERROR
    throw error;
  } finally {
    span.end();
  }
}

Через пару минут в Uptrace появляются первые трейсы:

Список трэйсов
Список трэйсов

Новый query builder: наконец-то удобно

Раньше фильтрация по атрибутам была болью. В v2.0 сделали нормальный интерфейс:

Query builder с автокомплитом
Query builder с автокомплитом

Фишки:

  1. Автокомплит атрибутов — начинаешь вводить user_, предлагает все варианты

  2. Toggle частей запроса — можно временно отключить условие, не удаляя

  3. Search clause для текстового поиска:

where service_name = "checkout" | search error|timeout|failed

Это найдёт все span'ы сервиса checkout, где в логах или атрибутах встречаются слова error, timeout или failed.

Реальный пример из жизни — искал, почему у пользователя упала оплата:

where attributes.user_id = "user_12345" and timestamp >= now() - interval 1 hour | search payment|stripe|checkout

Нашёл за 10 секунд.

Data Transformations: обрабатываем данные на лету

Это киллер-фича для продакшена. Uptrace может менять данные до записи в ClickHouse.

Кейс 1: Снижаем cardinality URL

Проблема: у нас эндпоинт /user/{userId}/orders/{orderId}, но в трейсах записываются конкретные ID:

/user/123/orders/456
/user/124/orders/457
/user/125/orders/458
...

Cardinality взлетает до небес → индексы раздуваются → запросы тормозят.

Решение через transformations:

// В Uptrace UI: Project → Transformations
setAttr("http_target", 
  replaceGlob(
    attr("http_target"), 
    "/user/*/orders/*", 
    "/user/{userId}/orders/{orderId}"
  )
);

Теперь все URL нормализуются:

/user/{userId}/orders/{orderId}

Кейс 2: Парсим строковые числа

Некоторые библиотеки отправляют числовые атрибуты как строки:

{
  "elapsed_ms": "1234.56"  // строка вместо числа
}

Чиним трансформацией:

setAttr("elapsed_ms", parseFloat(attr("elapsed_ms")))

Теперь можно считать агрегаты:

SELECT avg(attributes.elapsed_ms) FROM spans;

Кейс 3: Удаляем PII (персональные данные)

У нас логируется email пользователя, но для GDPR его нужно маскировать:

if (has(attr("user.email"))) {
  setAttr("user.email", "***@***")
}

Transformations работают на языке expr-lang — простой и мощный.

Где настраивать: Project → Transformations → New Operation

Интерфейс создания трансформации
Интерфейс создания трансформации

Retention policies: экономим деньги

Разные типы данных нужны на разные сроки:

  • Трейсы — свежие, для дебага (7 дней хватит)

  • Логи — подольше, для аудита (30 дней)

  • Метрики — долго, для трендов (90 дней)

В v2.0 настраиваем отдельно для каждого проекта:

projects:
  - name: production
    retention:
      spans: 168h      # 7 дней
      logs: 720h       # 30 дней  
      events: 720h     # 30 дней
      metrics: 2160h   # 90 дней

Или через UI: Settings → Project → Data Retention

Экономия: у нас трейсов ~100GB/день. Хранить 90 дней всё = 9TB. С retention 7/30/90 по типам — всего 4TB. Разница в деньгах ощутимая.

Миграция с v1.x (или других систем)

Uptrace рекомендует параллельный запуск:

┌─────────────┐
│ Your App    │
└──────┬──────┘
       │
       │ OpenTelemetry
       │
┌──────▼───────────────┐
│ OTel Collector       │
└──┬────────────────┬──┘
   │                │
   │                │
┌──▼────┐      ┌───▼─────┐
│ v1.x  │      │ v2.0    │
│ (old) │      │ (new)   │
└───────┘      └─────────┘

Конфиг OpenTelemetry Collector:

exporters:
  otlphttp/v1:
    endpoint: http://uptrace-v1:14318
    headers:
      uptrace-dsn: "http://token_v1@uptrace-v1:14318/1"
  
  otlphttp/v2:
    endpoint: http://uptrace-v2:14318
    headers:
      uptrace-dsn: "http://token_v2@uptrace-v2:14318/1"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/v1, otlphttp/v2]  # В оба!

План:

  1. Поднимаем v2.0 рядом с v1.x

  2. Дублируем трафик на обе инстанции

  3. Неделю смотрим, что всё ок

  4. Переключаем dashboard'ы на v2.0

  5. Выключаем v1.x

Именно так мы делали. Zero downtime.

Бенчмарки на реальных данных

Залил в v2.0 наши production-трейсы за неделю (~500M span'ов). Сравниваем запросы:

Запрос 1: Топ-5 самых медленных эндпоинтов

SELECT 
  attributes.http_target as endpoint,
  count() as requests,
  quantile(0.95)(duration) as p95_duration
FROM spans
WHERE service_name = 'api-gateway'
  AND timestamp >= now() - interval 24 hour
GROUP BY endpoint
ORDER BY p95_duration DESC
LIMIT 5;
  • v1.x: 4.2 секунды

  • v2.0: 0.38 секунды (11x быстрее)

Запрос 2: Все ошибочные трейсы конкретного пользователя

SELECT 
  trace_id,
  span_name,
  attributes.error_message
FROM spans
WHERE attributes.user_id = 'user_12345'
  AND attributes.error = true
  AND timestamp >= now() - interval 7 day
ORDER BY timestamp DESC;
  • v1.x: 6.8 секунды

  • v2.0: 0.52 секунды (13x быстрее)

Почему так быстро:

  1. JSON-атрибуты хранятся колоночно

  2. Индексы по user_id работают нативно

  3. Нет overhead на парсинг JSON при каждом запросе

SSL за 5 минут через Let's Encrypt

Production без HTTPS — моветон. В v2.0 встроили автоматизацию:

# uptrace.yml
certmagic:
  enabled: true
  staging_ca: false  # true для тестов
  http_challenge_addr: :80

listen:
  https:
    addr: :443
    domain: uptrace.mycompany.com

Uptrace сам:

  1. Запросит сертификат у Let's Encrypt

  2. Настроит редирект с HTTP на HTTPS

  3. Будет автоматически обновлять каждые 60 дней

Важно: домен должен смотреть на ваш сервер, и порт 80 должен быть открыт для HTTP-01 challenge.

У нас это заработало с первого раза. Раньше с nginx + certbot возился час.

Чеклист для продакшена

Вот что я настроил перед тем, как пустить в prod:

Безопасность

  • ✓ Сменил дефолтный пароль админа

  • ✓ Настроил SSL через Let's Encrypt

  • ✓ Включил MFA для админов (Premium edition)

  • ✓ Настроил OIDC через Google Workspace (Premium edition)

Data Transformations

  • ✓ Маскирование email и телефонов (PII)

  • ✓ Нормализация URL с динамическими параметрами

  • ✓ Парсинг строковых чисел в float

  • ✓ Sampling: 10% детальных трейсов, 100% ошибок

Retention

  • ✓ Трейсы: 7 дней

  • ✓ Логи: 30 дней

  • ✓ Метрики: 90 дней

  • ✓ Автоматическая очистка старых данных

Мониторинг

  • ✓ Алерты на disk usage ClickHouse

  • ✓ Алерты на lag обработки трейсов

  • ✓ Dashboard для самого Uptrace (meta!)

Backup

  • ✓ ClickHouse backups раз в сутки

  • ✓ PostgreSQL backups (метаданные)

  • ✓ Конфиги в git

Что в итоге

Плюсы Uptrace v2.0:

  • Реально быстрый — 10x не маркетинг, а факт

  • Всё в одном — traces, logs, metrics, не нужно три инструмента

  • Гибкие transformations — чинишь данные до записи

  • Простота развёртывания — docker-compose и готово

  • Нормальный query builder — не нужно учить особый язык запросов

Минусы / когда не подходит:

  • ClickHouse required — если хочешь что-то легче, это не сюда

  • Premium features — SSO/MFA/SAML только в платной версии

Кому подойдёт:

  • Микросервисы на 5+ сервисов

  • Нужна скорость запросов по атрибутам

  • Хочется всё в одном месте (traces + logs + metrics)

  • Готовы поднять ClickHouse

Кому НЕ подойдёт:

  • Совсем маленький проект (1-2 сервиса) — overkill

  • Уже настроили Grafana stack и он устраивает

  • Нужен managed SaaS без возни с инфрой (есть Uptrace Cloud, но платный)

Полезные ссылки


Итого

Если внедряете observability с нуля или мигрируете со старых решений — Uptrace v2.0 стоит попробовать. У меня ушло полдня на поднятие + неделя на миграцию production трафика. Результат: команда находит проблемы в 5-10 раз быстрее.

Хотите попробовать? Весь код из статьи доступен в репозитории Uptrace. Делитесь опытом внедрения в комментариях и если есть вопросы, пишите!