Перейти к основному содержимому

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

Важные особенности:

  1. Числитель и знаменатель могут быть из разных конфигов и даже разных источников
  2. Система знает, как посчитать метрики независимо от их расположения
  3. Best practice: Размещайте ratio-метрику в том же конфиге, где находится числитель, для лучшей организации кода

Filter DSL

Базовый синтаксис

filter: [колонка: значение]  # Одно условие
filter: [eid: 300, platform_id: 1] # AND

Операторы

ОператорПримерSQL
=eid: 300eid = 300
!=platform_id.!=: 1platform_id != 1
<, >, <=, >=price.>=: 1000price >= 1000
ineid.in: [300, 303]eid IN (300, 303)
!inplatform_id.!in: [1]platform_id NOT IN (1)
likecategory.like: 'доставка'category LIKE 'доставка'
!likecategory.!like: 'доставка'category NOT LIKE 'доставка'
ilikecategory.ilike: 'доставка'category ILIKE 'доставка'
!ilikecategory.!ilike: 'доставка'category NOT ILIKE 'доставка'
bitflags.bit: 3bitwise_and(flags, 3) > 0
!bitflags.!bit: 3bitwise_and(flags, 3) = 0
isnullitem_id.isnull: falseitem_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:

  1. thresholds: [10, 0] применяется снизу вверх по key_seq: [user_id, event_id]
  2. Первый threshold (10) применяется к первому ключу (user_id)
  3. Второй threshold (0) применяется ко второму ключу (event_id)
  4. На последнем уровне 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

Рекомендуется

  1. Группируйте метрики по предметной области. создавайте отдельные конфиги для разных бизнес-доменов
  2. Используйте осмысленные названия. договоритесь внутри команды о системе нейминга метрик заранее (например, {domain}_{action}_{unit}marketplace_purchases_count). С ростом количества метрик навигация станет сложнее
  3. Используйте YAML anchors. избегайте дублирования фильтров через definitions
  4. Размещайте ratio-метрики рядом с числителем. если числитель в buyer_stream.yaml, размещайте ratio там же для лучшей организации

❌ Не рекомендуется

  1. Дублирование фильтров. используйте YAML anchors
  2. Слишком сложные фильтры. вынесите логику в source или enrichment

Следующие шаги

  1. Dimensions. разрезы для группировки
  2. Workflow. практика создания
  3. Реестр метрик. просмотр SQL

Подробнее: Концепции → Metrics