Metrics (метрики)
Metrics. декларативное описание логики расчета метрик через фильтры и агрегации.
Три типа метрик
Counter (базовый) → Uniq (уникальность) → Ratio (производные)
1. Counter. счетчик
Фильтрует события и суммирует значения:
metric.counter:
metric_name:
filter: [условия] # Опционально
obs: [колонки] # Опционально (по умолчанию 1)
Примеры:
metric.counter:
page_views: { obs: [events_count] }
searches: { filter: [eid: 300], obs: [events_count] }
revenue: { filter: [eid: 500], obs: [price] }
2. Uniq. уникальные
Считает уникальные значения после применения counter:
metric.uniq:
metric_name:
counter: counter_name
key: [колонки]
thresholds: [число] # Опционально (по умолчанию 0)
Примеры:
metric.uniq:
daily_active_users:
counter: page_views
key: [cookie_id]
power_users:
counter: page_views
key: [cookie_id]
thresholds: [10] # 10+ событий
3. Ratio. производные
Делит одну метрику на другую:
metric.ratio:
metric_name:
num: числ итель
den: знаменатель
Пример:
metric.ratio:
conversion_rate:
num: buyers
den: daily_active_users
Важные особенности:
- Числитель и знаменатель могут быть из разных конфигов и даже разных источников
- Система знает, как посчитать метрики независимо от их расположения
- Best practice: Размещайте ratio-метрику в том же конфиге, где находится числитель, для лучшей организации кода
Filter DSL
Базовый синтаксис
filter: [колонка: значение] # Одно условие
filter: [eid: 300, platform_id: 1] # AND
Операторы
| Оператор | Пример | SQL |
|---|---|---|
= | eid: 300 | eid = 300 |
!= | platform_id.!=: 1 | platform_id != 1 |
<, >, <=, >= | price.>=: 1000 | price >= 1000 |
in | eid.in: [300, 303] | eid IN (300, 303) |
!in | platform_id.!in: [1] | platform_id NOT IN (1) |
like | category.like: 'дост авка' | category LIKE 'доставка' |
!like | category.!like: 'доставка' | category NOT LIKE 'доставка' |
ilike | category.ilike: 'доставка' | category ILIKE 'доставка' |
!ilike | category.!ilike: 'доставка' | category NOT ILIKE 'доставка' |
bit | flags.bit: 3 | bitwise_and(flags, 3) > 0 |
!bit | flags.!bit: 3 | bitwise_and(flags, 3) = 0 |
isnull | item_id.isnull: false | item_id IS NOT NULL |
$or | $or: [cond1, cond2] | (cond1 OR cond2) |
Примеры
Сравнения:
filter: [price.<: 1000]
filter: [age.>=: 18]
Сравнение колонок между собой:
filter: [col1.>: $col2] # Символ $ позволяет ссылаться на другое поле
filter: [price.>=: $min_price]
IN:
filter: [eid.in: [300, 303]]
filter: [platform_id.!in: [1]]
LIKE/ILIKE (поиск по строкам):
filter: [category.like: '%доставка%'] # Case-sensitive
filter: [category.ilike: '%ДОСТАВКА%'] # Case-insensitive
filter: [title.!like: '%тест%'] # NOT LIKE
Bitwise:
filter: [item_flags.bit: 5] # Проверка 5-го бита
filter: [item_flags.!bit: 5] # Отсутствие бита
NULL:
filter: [item_id.isnull: false] # IS NOT NULL
Несколько полей в obs:
metric.counter:
total_revenue:
obs: [field_a, field_b, field_c]
# Логика: (сумма по полю А) + (сумма по полю Б) + (сумма по полю С)
OR:
filter:
- vertical_id: 1
- $or:
- [price.>=: 10000]
- [item_flags.bit: 7]
# WHERE vertical_id = 1 AND (price >= 10000 OR (item_flags & (1 << 7)) > 0)
YAML Anchors
YAML поддерживает переиспользование фильтров через anchors, что помогает избежать дублирования и упрощает поддержку кода.
❌ Без YAML anchors (с дублированием):
metric.counter:
premium_user_payments:
filter:
- is_premium_user: true
- event_id.in: [55, 57, 67]
obs: []
premium_user_refunds:
filter:
- is_premium_user: true # Дублирование!
- event_id.in: [88, 89]
obs: []
** С YAML anchors (без дублирования):**
definitions:
- &is_premium_user { is_premium_user: true }
- &payment_events { event_id.in: [55, 57, 67] }
- &refund_events { event_id.in: [88, 89] }
metric.counter:
premium_user_payments:
filter: [*is_premium_user, *payment_events]
obs: []
premium_user_refunds:
filter: [*is_premium_user, *refund_events]
obs: []
Более компактный вариант:
definitions:
- &eid_search [300]
- &eid_contact [315]
- &is_search { eid: *eid_search }
- &is_contact { eid: *eid_contact }
metric.counter:
searches: { filter: *is_search }
contacts: { filter: *is_contact }
Примеры
DAU
metric.counter:
page_views: { obs: [events_count] }
metric.uniq:
daily_active_users:
counter: page_views
key: [cookie_id]
Конверсионная воронка
definitions:
- &is_search { eid: 300 }
- &is_view { eid: 303 }
- &is_contact { eid: 315 }
- &is_purchase { eid: 500 }
metric.counter:
searches: { filter: *is_search }
views: { filter: *is_view }
contacts: { filter: *is_contact }
purchases: { filter: *is_purchase }
metric.uniq:
searchers: { counter: searches, key: [cookie_id] }
viewers: { counter: views, key: [cookie_id] }
contactors: { counter: contacts, key: [cookie_id] }
buyers: { counter: purchases, key: [cookie_id] }
metric.ratio:
search_to_view_rate: { num: viewers, den: searchers }
view_to_contact_rate: { num: contactors, den: viewers }
contact_to_purchase_rate: { num: buyers, den: contactors }
С enrichments
# Требует enrichment buyer_segment_cookie \{#trebuet-enrichment-buyer-segment-cookie}
definitions:
- &is_seeker { buyer_segment: "seeker" }
metric.counter:
seeker_events:
filter: *is_seeker
metric.uniq:
daily_active_seekers:
counter: seeker_events
key: [cookie_id]
Иерархические группировки с key_seq
key_seq позволяет применять пороги на разных уровнях группировки. Пример: посчитать пользователей, у которых более 10 оплат.
metric.counter:
premium_user_payments:
filter:
- is_premium_user: true
- event_id.in: [55, 57, 67]
obs: []
metric.uniq:
premium_users_with_10plus_payments:
counter: premium_user_payments
key_seq: [user_id, event_id]
thresholds: [10, 0]
Как это работает:
Система генерирует SQL с вложенными группировками, применяя threshold на каждом уровне:
-- Уровень 2: считаем пользователей (threshold = 10)
SELECT SUM(CASE WHEN payments > 10 THEN 1 END) AS num
FROM (
-- Уровень 1: считаем оплаты каждого типа для каждого пользователя (threshold = 0)
SELECT user_id,
SUM(CASE WHEN payments > 0 THEN 1 END) AS payments
FROM (
-- Уровень 0: группируем по user_id и event_id (без threshold)
SELECT user_id,
event_id,
COUNT(*) AS payments
FROM source
WHERE is_premium_user = true AND event_id IN (55, 57, 67)
GROUP BY user_id, event_id
) AS temp1
GROUP BY user_id
) AS temp2
Логика thresholds:
thresholds: [10, 0]применяется снизу вверх поkey_seq: [user_id, event_id]- Первый threshold (10) применяется к первому ключу (user_id)
- Второй threshold (0) применяется ко второму ключу (event_id)
- На последнем уровне threshold не используется (просто COUNT)
Связь с sources
# sources.yaml
buyer_stream:
metric_configs: [buyer_stream, conversion] # Рекомендуется
Работа с CLI
# Генерация SQL для одной метрики \{#generatsiya-sql-dlya-odnoy-metriki}
trisigma sl compile --metrics daily_active_users
# Несколько метрик \{#neskol-ko-metrik}
trisigma sl compile --metrics daily_active_users,buyers,conversion_rate
# Список метрик \{#spisok-metrik}
trisigma sl list-metrics
# Валидация \{#validatsiya}
trisigma sl validate
Реестр метрик
После деплоя метрики доступны в Реестре. Там их можно описать, проставить ответственных, теги и тд.
Best Practices
Рекомендуется
- Группируйте метрики по предметной области. создавайте отдельные конфиги для разных бизнес-доменов
- Используйте осмысленные названия. договоритесь внутри команды о системе нейминга метрик заранее (например,
{domain}_{action}_{unit}→marketplace_purchases_count). С ростом количества метрик навигация станет сложнее - Используйте YAML anchors. избегайте дублирования фильтров через definitions
- Размещайте ratio-метрики рядом с числителем. если числитель в
buyer_stream.yaml, размещайте ratio там же для лучшей организации
❌ Не рекомендуется
- Дублирование фильтров. используйте YAML anchors
- Слишком сложные фил ьтры. вынесите логику в source или enrichment
Следующие шаги
- Dimensions. разрезы для группировки
- Workflow. практика создания
- Реестр метрик. просмотр SQL
Подробнее: Концепции → Metrics