Раздел 42 · Аналитика данных с нуля

План трекинга: какие события писать и как проверить, что они пишутся

middle~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Бран · продюсер «Хроник Пограничья»

Хочу понять, откуда игроки приходят в магазин и что мы им там показываем. Программисты говорят, что всё пишется в events. Я им верю, а ты? Если отчёт соврёт, на совете директоров краснеть буду я.

В прошлом уроке мы раскладывали выручку «Кубика» на множители. Такой разбор точен ровно настолько, насколько точно записаны данные, а в продукте данные это прежде всего события. Сегодня спускаемся на этаж ниже: какие события нужны, как их назвать и как SQL-запросом поймать трекинг на вранье. База прежняя, guild, таблица events, которую мы уже резали на воронку.

Бортовой журнал

Вот две записи из events. Обе про одно и то же: игрок открыл магазин.

 name       | platform | props
------------+----------+---------------------------------------------------------------------------
 store_open | ios      | {"source": "main_menu", "app_version": "2.0.1", "offers_shown": [3, 15, 12, 2]}
 store_open | android  | {"Source": "quest_reward", "app_version": "2.0.0", "offers_shown": [18, 4]}

Заметил разницу? Во второй записи ключ Source с большой буквы. Человек прочтёт обе одинаково. Запрос props->>'source' вторую не прочтёт: для JSON это два разных ключа.

Так бывает с бортовым журналом корабля. Если каждый вахтенный пишет по-своему («шли на норд», «курс 0°», «вверх по карте»), по журналу не восстановить рейс. Поэтому у флота есть форма записи, общая для всех вахт.

У продукта такая форма называется план трекинга. Аналитик пишет его до того, как программист добавит первую строчку отправки события, и потом сверяет с ним данные.

От вопроса к событию

План начинается не с событий, а с вопросов. Событие без вопроса никто не прочтёт, а вопрос без события останется без ответа. Для «Хроник» три вопроса Брана превращаются в такой план:

вопрос продуктасобытиеобязательные свойства
сколько новичков проходят обучение и за сколькоtutorial_start, tutorial_completeapp_version; у завершения ещё duration_sec
откуда игроки приходят в магазин и что видятstore_openapp_version, source, offers_shown
что покупают первым и за сколькоfirst_purchaseapp_version, offer_id, price_rub, currency

Плюс install со свойствами device и utm_source (откуда пришёл игрок) и first_quest_complete. Шесть событий на весь первый день, 15 897 строк в базе.

Три правила хорошего плана:

  1. Имена в одном стиле. В «Хрониках» это объект_действие маленькими буквами через подчёркивание: store_open, tutorial_complete. Не OpenStore, не store opened, не магазин_открыт. Один стиль значит, что name LIKE 'tutorial_%' найдёт всё обучение, а опечатку видно сразу.
  2. Обязательные свойства записаны явно. Свойство, без которого событие не отвечает на свой вопрос, обязательно. store_open без source не скажет, откуда пришёл игрок, и в отчёте про источники бесполезен.
  3. Версия приложения в каждом событии. Когда метрика сломается, первым делом спросят «в какой версии», и без app_version ответить нечем. В «Хрониках» это 1.9.0-1.9.4 до релиза 14 мая 2026, 2.0.0 первую неделю после него и 2.0.1 дальше.

Свойства лежат в jsonb

У событий разный набор свойств, поэтому их хранят не отдельными столбцами, а одним столбцом props типа jsonb. Как его читать, мы разбирали в уроке про JSON в событиях: -> достаёт вложенный JSON, ->> достаёт значение текстом.

SELECT props->>'app_version' AS app_version,
       props->'device'->>'model' AS model,
       props->>'utm_source' AS utm_source
FROM events
WHERE name = 'install'
LIMIT 3;

Для проверки качества главный оператор из того же урока ?: props ? 'source' отвечает, есть ли в JSON такой ключ. Это не то же самое, что props->>'source' IS NULL: ключа может не быть вовсе, а может быть ключ со значением null. Для плана трекинга важно первое.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.