Введение
Архитектура
Обработка входящих запросов
Коммуникация сервисов
Руководство пользователя
Получение данных из сервисов
Использование Панели управления
Туториалы
Получение данных из других сервисов
Построение истории отдельного пользователя
Классические модели атрибуции
Вероятностное прохождение воронки
Атрибуция по индексу активности
Воронки
X
Выберите раздел

Обогатитель событий

Обогатитель получает от Front несколько полей события, вычисляет по ним дополнительные данные и возвращает их. Front дописывает результат в data события до того, как оно попадёт обработчику и слушателям. Пример — mmgeo: по IP определяет страну, регион и город, по User-Agent — браузер, ОС и устройство.

В отличие от слушателя, обогатитель стоит на критическом пути запроса: Front ждёт его ответ, по умолчанию не дольше 200 мс. Поэтому базы загружаются при старте, а результаты кэшируются.

Подписка и имя

Обогатитель слушает общий канал enrich и свой прямой канал. Поскольку обогатителей одного типа обычно несколько, имя собирается из группы и номера воркера, а группа отдельно указывается в rpc.service_group:

rpc:
  service_group: <%= env.SERVICE_GROUP || 'unknown_enrich' %>
  name: <%= env.SERVICE_GROUP || 'unknown_enrich' %>_<%= env.WORKER_ID || '1' %>
  channels:
    - enrich
    - <%= env.SERVICE_GROUP || 'unknown_enrich' %>_<%= env.WORKER_ID || '1' %>

service_group — это и ключ, под которым результат ляжет в data события: обогатитель группы mmgeo даёт data.mmgeo, группы rst — data.rst. chwriter затем разворачивает вложенный объект в колонки с префиксом: mmgeo_country_iso, rst_asn_org.

mmgeo — один код для нескольких групп. По значению SERVICE_GROUP он решает, какую базу загрузить: mmgeo — GeoLite2 City, ip2lgeo — IP2Location, rst — файл с репутацией IP, uap и uapc — разбор User-Agent двумя разными парсерами. Одна и та же сборка запускается несколько раз с разными переменными окружения.

Регистрация

Front узнаёт об обогатителе через heartbeat и __status (см. Шина сообщений и RPC). В ответе на __status обогатитель перечисляет, какие события ему интересны и какие поля прислать:

async setup() {
  this.rpc.register(ENRICH, this.handle.bind(this));
  this.registerEnricher(ENRICH, ['in.gen.track'], { ip: 'td.ip', ua: 'td.ua' });
  this.rpc.register(METHOD_STATUS, () => this.status());

  const aliver = () => {
    this.meter.tick(`band.${this.name}.alive`);
    this.rpc.notify(SERVICE_DIRECTOR, METHOD_IAMALIVE, { name: this.name });
  };
  setTimeout(aliver, 500);
  setInterval(aliver, 9 * 1000);
}
async status(): Promise<RPCAppStatus> {
  return {
    name: this.name,
    app_state: 'running',
    app_started: Number(this.appStarted),
    app_uptime: Date.now() - Number(this.appStarted),
    register: this.enrichers.map(e => ({
      method: e.method,
      role: 'enricher',
      options: { keys: e.keys, props: e.props }
    }))
  };
}
  • keys — префиксы ключей маршрутизации. in.gen.track покрывает все события Web-SDK: in.gen.track.page, in.gen.track.activity и остальные.
  • props — что прислать. Ключ — имя параметра, значение — путь в событии через точку. Front соберёт из события { ip: msg.td.ip, ua: msg.td.ua } и отправит только это.

Обработка запроса

Front вызывает метод enrich групповым запросом сразу всем зарегистрированным на ключ обогатителям. Метод получает ровно те поля, что были запрошены в props, и возвращает плоский объект с результатом или {}, если сказать нечего:

async handle({ ip, ua }: { ip: string, ua: string }) {
  if (this.service_group === 'mmgeo') {
    if (!ip.includes(':')) {               // база только для IPv4
      try {
        return handleCity(this.mmReader.city(ip));
      } catch (e) {
        this.log.error(e, 'mmgeo ex');
      }
    }
  }
  if (this.service_group === 'uap') {
    const cached = uapCache.get(ua);
    if (cached) return cached;
    const parsed = UAParser(ua);
    const result = {
      browser_name: parsed.browser.name,
      browser_version: parsed.browser.version,
      os_name: parsed.os.name,
      os_version: parsed.os.version,
      device_type: parsed.device.type,
      device_vendor: parsed.device.vendor,
      device_model: parsed.device.model
    };
    uapCache.set(ua, result);
    return result;
  }
  return {};
}

Что важно для производительности:

  • Данные в памяти. Базу GeoLite2 mmgeo читает в буфер один раз при старте, файл репутации IP разбирает в Map. Никаких обращений к диску или сети в handle.
  • Кэш результатов. Разбор User-Agent дорогой, а одинаковых строк много. mmgeo кэширует результаты в TTL-кэше на 10 000 записей и 30 минут.
  • Молчать лучше, чем ошибаться. На исключение отвечайте {} и пишите в лог. Ошибка в ответе Front только залогирует, а значение для группы останется пустым.

Все воркеры одной группы получают каждый запрос из канала enrich и все отвечают. Front складывает ответы по service_group, так что дублирование безвредно, но нагрузку оно не делит. Горизонтальное масштабирование обогатителя — это разные группы с разными keys, а не копии одной группы.

Результат в событии

После обогащения событие in.gen.track.page придёт слушателям с добавленными блоками:

{
  "key": "in.gen.track.page",
  "td": { "ip": "93.184.216.34", "ua": "Mozilla/5.0 ..." },
  "data": {
    "page": { "url": "https://example.com/" },
    "mmgeo": { "country_en": "Russia", "country_iso": "RU", "region_en": "Moscow", "region_iso": "MOW", "city_en": "Moscow" },
    "uapc": { "browser_family": "Chrome", "browser_version": "124.0.0", "os_family": "Android", "os_version": "14", "device_family": "K", "is_bot": false }
  }
}

Чтобы колонки появились в ClickHouse, добавьте их в конфигурацию chwriter — файл в config/custom/ с полями таблицы events под префиксом группы:

clickhouse:
  tables:
    events:
      mmgeo_country_en: String
      mmgeo_country_iso: String
      mmgeo_region_en: String
      mmgeo_city_en: String

Новый обогатитель за пять шагов

  1. Скопируйте mmgeo, оставьте в AppServer одну ветку service_group и свою логику в handle.
  2. В config/02_config.yml задайте rpc.service_group, rpc.name и rpc.channels через SERVICE_GROUP и WORKER_ID.
  3. В setup() зарегистрируйте enrich, __status с нужными keys и props и запустите heartbeat.
  4. Загрузите справочники в конструкторе, добавьте кэш на горячие значения.
  5. Запустите с SERVICE_GROUP=mygroup и проверьте, что в событиях у слушателей появился блок data.mygroup. Затем опишите его колонки в chwriter.