Делюсь практическим опытом внедрения 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:
productionOrganization:
My CompanyToken генерируется автоматически
Альтернативно через конфиг 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 сделали нормальный интерфейс:

Фишки:
Автокомплит атрибутов — начинаешь вводить
user_, предлагает все вариантыToggle частей запроса — можно временно отключить условие, не удаляя
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] # В оба!
План:
Поднимаем v2.0 рядом с v1.x
Дублируем трафик на обе инстанции
Неделю смотрим, что всё ок
Переключаем dashboard'ы на v2.0
Выключаем 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 быстрее)
Почему так быстро:
JSON-атрибуты хранятся колоночно
Индексы по
user_idработают нативноНет 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 сам:
Запросит сертификат у Let's Encrypt
Настроит редирект с HTTP на HTTPS
Будет автоматически обновлять каждые 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. Делитесь опытом внедрения в комментариях и если есть вопросы, пишите!
