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

Фишки:
Автокомплит атрибутов — начинаешь вводить
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. Делитесь опытом внедрения в комментариях и если есть вопросы, пишите!