Перейти к содержанию

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
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 для одной метрики
trisigma sl compile --metrics daily_active_users

# Несколько метрик
trisigma sl compile --metrics daily_active_users,buyers,conversion_rate

# Список метрик
trisigma sl list-metrics

# Валидация
trisigma sl validate

Реестр метрик#

После деплоя метрики доступны в Реестре. Там их можно описать, проставить ответственных, теги и тд.


Best Practices#

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

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

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

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

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

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