Продуктовый аналитик: Путешествие туда и обратно
Глава 2. Кузница оружия
2.1. Формат событий (ивентов)
Наставник даёт вам первые инструменты. Здесь, в грохоте ETL-процессов и мерцании строк кода, вы куете своё оружие для будущих битв в загадочном и прекрасном мире аналитики больших данных.
-Tidy data
-Формат событий, что отправляем и в каком формате
-Архитектура сбора логов в F2P играх
-Пример на основе Heroes 3
2.1.1. Что такое Tidy Data
В мире данных порядок на входе это гарантия качественных результатов на выходе. Разработанный Хэдли Уикхэмом принцип Tidy Data (аккуратные данные) это стандарт структурирования данных в современной аналитике, который экономит часы работы и предотвращает ошибки.
Главные преимущества
- Эффективное хранение. Структурированные данные сжимаются на 30-50% лучше.
- Гибкость обработки. Возможность партицирования по датам/игрокам/серверам.
- Производительность. Оптимальное индексирование в базах данных.
- Совместимость. Все современные инструменты аналитиков (Pandas, R, Tableau, PowerBI) заточены под этот стандарт.
Три правила Tidy Data
1. Каждая переменная в отдельном столбце. “Переменная” означает атомарное измерение, которое нельзя разложить без потери смысла.
2. Каждое событие (event) в отдельной строке. Event это одно событие в конкретный момент времени с конкретным контекстом для конкретного субъекта (игрока, матча и т.д.).
3. Каждый тип событий в отдельной таблице. Разделение таблиц по доменам: разные игровые события, платежи, и т.д. Например, purchased_item, completed_quest, battle_result должны быть в трех разных логах.
Практический пример преобразования
Исходные “неподходящие” данные в виде json
{
“user_id”: “123”,
“day_1_sessions”: 3,
“day_1_revenue”: 5.99,
“day_2_sessions”: 2,
“day_2_revenue”: 0
},
В виде таблицы
| user_id | day_1_sessions | day_1_revenue | day_2_sessions | day_2_revenue |
|---|---|---|---|---|
| 123 | 3 | 5.99 | 2 | 0 |
Как видно, по игроку c идентификатором «123» одинаковые по типу метрики 1 и 2 дня (число сессий и выручка) записаны 4 столбцами. Проблема в том, что дни закодированы в названиях столбцов, что делает анализ динамики крайне неудобным, а если появятся новые даты, то неясно куда из записывать.
Для преобразования по стандарту Tidy Data нам необходимо транспонировать (принятое название «unpivot») таблицу (обычно это команды pivot/unpivot table), a json разделить на два по дням, и добавить в лог в какой день было создано событие.
Tidy Data преобразование:
json
[
{“user_id”: “123”, “date”: “2024-01-15”, “sessions”: 3, “revenue”: 5.99},
{“user_id”: “123”, “date”: “2024-01-16”, “sessions”: 2, “revenue”: 0}
],
В виде таблицы:
| user_id | date | sessions | revenue |
|---|---|---|---|
| 123 | 2024-01-15 | 3 | 5.99 |
| 123 | 2024-01-16 | 2 | 0.00 |
В реальных игровых данных важно также добавлять event_timestamp с миллисекундной точностью и session_id для корректного анализа последовательностей действий.
Рекомендации
- Начинайте с Tidy Data с первого дня. Технический долг в структуре данных обходится в 3-5 раз дороже исправления на старте.
- Проектируйте что и как логировать на основе бизнес-вопросов. Думайте как вы будете использовать данные чтобы помогать сделать игру лучше: “Какие данные мне понадобятся, чтобы понять, почему игроки уходят на 3 день?”, “Как проверить гипотезу об улучшении?”.
- Скорее всего вам не обойтись без логов на вход и выход, всего что касается основных циклов (core- и meta-game), прихода/расхода всех сущностей и всех валют, монетизации, детальных логов битвы, матчмейкинга (если он есть).
- Документируйте схему данных. Каждое поле должно иметь описание, пример значения и правила валидации. Это критически важно для работы команды из аналитиков, дата-инженеров и разработчиков.
- Всегда включайте event_timestamp в формате UTC с максимально доступной точностью (желательно микросекунды) и уникальный session_id обязателен для анализа поведения внутри сессии.
2.1.2: Архитектура сбора логов в F2P играх
Современная гибридная архитектура обычно включает в себя:
- Логи с устройства игрока (Client-side logging). Логируются все взаимодействия с UI, технические метрики (FPS, память). Используются для анализа UX и выявления технических проблем. Отправляются асинхронно, с низким приоритетом, допускают потерю части данных.
- Логи с сервера (Server-side logging, на сам игровой сервер напрямую аналитики не заходят). Логируются все изменения игрового состояния (трата/начисление валюты, завершение миссии, итог боя). Являются источником истины для экономики и баланса. Обязательно дублируются в надежную очередь (Kafka) перед сохранением.
- Логи платежных систем (Steam, App Store, Google Play и т.д.).
- Логи маркетинговых систем (атрибуции, реатрибуции, клики, переходы и т.д.).
Client-side logging (через SDK). Обычно через SDK Unity/Unreal события с устройства игрока в виде json отправляются в Облако (например, во внешний аналитический сервис, такой как Amplitude или Mixpanel) или в собственную базу данных для последующей работы аналитика с сырыми данными. Главный плюс: полнота данных (все клики, FPS, события интерфейса). Главный минус и риск: данные ненадежны и могут быть сфальсифицированы, что неприемлемо для логирования экономических транзакций, итогов PvP-матчей или anti-cheat.
Server-side logging. События с сервера гораздо более надежные и не зависят от проблем на стороне клиента, но ограничены серверной статистикой. А требования к отказоустойчивости реализации гораздо выше (требуются отказоустойчивые буферы, такие как Kafka). Можно потерять статистику одного игрока при отправке с клиента, но потерять данные вообще всех игроков уже катастрофа.
Технологический стек
1. Сбор и буферизация: Apache Kafka / AWS Kinesis / Google PubSub.
2. Обработка в реальном времени: Apache Flink / Spark Streaming для мгновенных алертов (например, падение доходов) и онлайн-мониторинга.
3. Пакетная обработка (Batch) и ETL: Apache Spark для ежедневных отчетов, сложных агрегаций и загрузки в хранилище.
4. Хранилище (важно реализовать мониторинг объема событий, их структуры и правдоподобности значений)
- Data Lake (сырые логи): AWS S3 / Google Cloud Storage хранит все логи в неизменном виде.
- Data Warehouse (обработанные данные): Google BigQuery / Snowflake / ClickHouse используется для аналитики.
2.1.3. Пример логов для Heroes 3
В задачи аналитика входит предложить формат логирования событий для последующей передачи команде разработки. И именно аналитику будет необходимо на основании полученных из логов таблиц проводить исследования. Таким образом, именно формат ивентов определяет качество всех последующих открытий (insights). Плохо спроектированные логи это как смотреть на итоговые данные через разбитое стекло: картина будет искаженной и неполной.
На примере прекрасной пошаговой стратегии Heroes 3 упрощенно разберем укороченный core-цикл «ресурсы → армия → битва → ресурсы», опустив этапы исследование территории и захват объектов. Если кто-то не играл в эту игру, то это достаточно стандартно, когда в игре мы собираем ресурсы, на которые потом строим армию, с этой армией участвуем в битве, чтобы получить новые ресурсы и цикл идет заново.

Принципы проектирования
1.Каждое событие должно содержать или позволять восстановить контекст.
- Игрок, например, уровень игрока, сила, текущие ресурсы;
- Сессия, например, время от старта сессии, количество действий;
- Игра, например, сервер, версия игры, локация.
2.Сквозная идентификация, когда события должны связываться через:
- user_id – идентификатор игрока,
- session_id – идентификатор сессии,
- cycle_id - уникальный идентификатор для каждого хода, если у вас пошаговая стратегия,
- match_id – идентификатор PVP матча, если у вас есть сражение между игроками то это общий идентификатор их матча
- transaction_id -идентификатор транзакции, отправляется для связки событий в лог траты валюты и в лог начисления купленного предмета.
3.Атомарность, когда событие фиксирует факт, который уже произошел. Одно событие - один смысловой акт (например, “потратил 10 золота на покупку” и “купил меч за золото”, даже если это произошло одновременно, это уже два события)
4.Для ключевых событий логируйте состояние “до” и “после”. Это золотое правило экономического трекинга.
5.Лог может идти один за другим по времени, когда игрок сделал конкретное действие, например, получил или потратил. Или изменение состояния, например, старт новой недели. А может идти как фиксация состояния на заданный момент (снепшот), например, фиксируем состояние армии перед боем и после боя.
Получение ресурсов

{
“event_type”: “resource_gathered”, // название лога
“cycle_id”: “cycle_h3_001”, // идентификатор хода
“timestamp”: “2024-01-15T10:00:00Z”, // когда игрок получил ресурс
“user_id”: “hero_player_123”, // идентификатор игрока
“session_id”: “session_h3_abc”, // идентификатор сессии
“resource_type”: “gold”, // название ресурса, который начислен, для каждого свой лог
“amount_gathered”: 150, // количество ресурса, который начислен
“gathering_method”: “mine”, // способ, которым добыт ресурс, для каждого свой лог
“resources_before”: {“gold”: 300, “wood”: 150}, // количество ресурса до ивента
“resources_after”: {“gold”: 450, “wood”: 150} // количество ресурса после ивента
}
| event_type | cycle_id | timestamp | user_id | session_id | resource_type | amount_gathered | gathering_method | gold_before | gold_after | wood_before | wood_after |
|---|---|---|---|---|---|---|---|---|---|---|---|
| resource_gathered | cycle_h3_001 | 2024-01-15T10:00:00Z | hero_player_123 | session_h3_abc | gold | 150 | mine | 300 | 450 | 150 | 150 |
| resource_gathered | cycle_h3_001 | 2024-01-15T10:05:00Z | hero_player_123 | session_h3_abc | wood | 75 | forest | 450 | 450 | 150 | 225 |
| resource_gathered | cycle_h3_002 | 2024-01-15T11:30:00Z | hero_player_456 | session_h3_def | gold | 200 | quest_reward | 1000 | 1200 | 300 | 300 |
| resource_gathered | cycle_h3_002 | 2024-01-15T11:35:00Z | hero_player_456 | session_h3_def | crystal | 5 | dungeon | 10 | 15 | 300 | 300 |
Покупка армии

{
“event_type”: “army_purchased”,
“event_timestamp”: “2024-01-15T10:05:00.654321Z”,
“user_id”: “hero_player_123”,
“session_id”: ” session_h3_abc “,
“transaction_id”: “txn_recruit_789”, // Для связки с событием начисления юнитов, которое логируем отдельным событием
“unit_type”: “archer”,
“unit_tier”: 2,
“quantity”: 10,
“total_cost”: {“gold”: 300}, // Явно указываем валюту и сумму
“location_building_id”: “barracks_1”,
“balance_before”: {“gold”: 450, “wood”: 150},
“balance_after”: {“gold”: 150, “wood”: 150}
}
| event_type | cycle_id | timestamp | user_id | session_id | unit_type | unit_tier | units_created | cost_gold | total_army_power |
|---|---|---|---|---|---|---|---|---|---|
| army_created | cycle_h3_001 | 2024-01-15T10:10:00Z | hero_player_123 | session_h3_abc | archer | 2 | 10 | 300 | 850 |
| army_created | cycle_h3_001 | 2024-01-15T10:12:00Z | hero_player_123 | session_h3_abc | warrior | 1 | 15 | 450 | 1200 |
| army_created | cycle_h3_002 | 2024-01-15T11:40:00Z | hero_player_456 | session_h3_def | knight | 3 | 5 | 1000 | 1500 |
Участие в битве (по завершению битвы)

{
“event_type”: “battle_completed”,
“event_timestamp”: “2024-01-15T10:20:00.987654Z”,
“user_id”: “hero_player_123”,
“session_id”: ” session_h3_abc “,
“battle_id”: “battle_pvp_alpha”,
“battle_type”: “pvp”,
“result”: “victory”,
“duration_ms”: 180000,
“enemy_user_id”: “player_456”, // пусть это наш противник в PVP, у него будет его лог
“enemy_power”: 1200,
“losses”: [ // Детализированные потери
{“unit_type”: “archer”, “tier”: 2, “quantity”: 3},
{“unit_type”: “warrior”, “tier”: 1, “quantity”: 8}
],
“rewards”: { // Детализированные награды
“experience”: 200,
“resources”: {“gold”: 500, “crystal”: 1}
},
// Снапшот армии можно вынести в отдельное событие ‘battle_started/ended’)
“army_before”: […],
“army_after”: […]
}
| event_type | cycle_id | timestamp | user_id | session_id | battle_type | battle_result | duration_seconds | enemy_power | xp_gain | gold_gained |
|---|---|---|---|---|---|---|---|---|---|---|
| battle_completed | cycle_h3_001 | 2024-01-15T10:25:00Z | hero_player_123 | session_h3_abc | pvp | victory | 180 | 1200 | 200 | 500 |
| battle_completed | cycle_h3_001 | 2024-01-15T10:45:00Z | hero_player_123 | session_h3_abc | pve | defeat | 120 | 800 | 50 | 0 |
| battle_completed | cycle_h3_002 | 2024-01-15T12:00:00Z | hero_player_456 | session_h3_def | pvp | victory | 240 | 1500 | 300 | 750 |
На какие вопросы, например, теперь мы могли бы ответить
Эффективность цикла
- Время полного цикла (от первого сбора до завершения битвы)
- Конверсия между этапами: сколько игроков, собрав ресурсы в первую неделю, дошли до битвы?
Баланс игры
- Оптимальные пропорции ресурс/армия для победы.
- Дисбалансы в стоимости юнитов vs их эффективности.
- Где игроки чаще всего прерывают игру из-за сложности.
Прогрессия
- Изменение эффективности игрока по мере роста уровня.
- Паттерны поведения: агрессивные vs ресурсные стратегии.
В итоге правильно спроектированные ивенты позволяют отвечать на сложные вопросы и находить точки роста игры. Они превращают данные из отчета о прошлом в инструмент для принятия решений о будущем игры, позволяя влиять на удержание, монетизацию и баланс.