Контракт события и минимизация данных
Поток «События» измеряет просмотры, завершения и сохранения уроков. Чтобы SQL отвечал на устойчивые вопросы, каждая строка должна иметь одинаково определённые поля. Рассмотрим событие как сообщение между приложением и аналитическим сборщиком. Результат урока — контракт первой версии и правила, которые не допускают лишние пользовательские сведения.
Нужно помнить различие события и текущей отметки кабинета. Сборщик не получает всю запись аккаунта ради одного завершения. Учебные примеры используют вымышленные идентификаторы и не были отправлены реальному серверу. Эта глава описывает техническую минимизацию данных, а не даёт правового заключения о конкретном режиме обработки или согласия.
Что необходимо для отчёта
Идентификатор события позволяет различить действие и повтор доставки. Идентификатор посетителя позволяет считать повторное использование сайта в рамках выбранной модели. Идентификатор урока связывает действие с содержанием. Имя события определяет измеренный факт. Два времени показывают, когда факт произошёл и когда его увидел сборщик.
Полный пример одной строки JSON использует строки времени в UTC и номер версии контракта. Значения UUID здесь вымышленные, но имеют форму, пригодную для последующей схемы.
{
"schema_version": 1,
"event_id": "00000000-0000-4000-8000-000000000001",
"anon_id": "00000000-0000-4000-8000-00000000000a",
"lesson_id": "html-start",
"course_id": "web",
"event_name": "lesson_view",
"occurred_at": "2026-10-01 09:00:00.000",
"received_at": "2026-10-01 09:00:02.000",
"version": 1
}
schema_version относится к форме сообщения, а version — к выбору последней доставки одного логического события в учебной таблице. Эти числа не взаимозаменяемы. При повторе того же действия event_id сохраняется; новый реальный просмотр получает новый идентификатор. Если каждый повтор сети генерирует новый UUID, хранилище не сможет определить, что это одна доставка.
received_at устанавливается доверенным сборщиком. Браузерное время occurred_at может быть ошибочным, поэтому сервер должен применять допустимые границы давности и будущего времени. В фикстуре мы задаём оба времени вручную для обучения; это не подтверждает, что серверная проверка уже существует.
Почему не нужен полный URL
Урок имеет устойчивый lesson_id, поэтому полный адрес с query string не нужен для основных отчётов. Параметры URL могут содержать поисковые строки, внутренние токены или сведения, введённые человеком. Их бессистемное копирование в аналитический JSON увеличило бы риск без пользы для вопроса о завершении материала.
По той же причине не передаём имя, email, текст профиля, пароль, cookie сессии и содержимое формы. Число закладок не требует этих полей. Если позже понадобится другой вопрос, сначала оцените, можно ли ответить агрегатом или более грубой категорией, затем измените контракт осознанно. Свободный объект properties с любыми значениями затрудняет такую оценку.
Сборщик должен принимать разрешённый набор полей и отклонять неизвестные либо игнорировать их по явно заданной политике. Он не должен складывать весь request в запасной столбец «на будущее». Технический журнал доставки тоже нуждается в минимизации: отсутствие лишних полей в ClickHouse не помогает, если они остаются в другом журнале.
Анонимный идентификатор и его границы
Для учебной модели анонимный идентификатор — случайное значение браузера после разрешённого начала измерения. Он не равен аккаунту и не превращается в него автоматически после входа. Очистка браузерного хранилища создаёт нового условного посетителя, другое устройство имеет другой идентификатор. Поэтому «уникальный посетитель» в отчёте означает уникальный anon_id.
Такое значение не следует называть гарантированно необратимо анонимным. Стабильный идентификатор позволяет связывать действия между собой, а дополнительные данные способны расширить связность. В документации продукта нужно честно описывать область идентификации и способ прекращения измерения. Не добавляйте скрытое связывание с email для «удобного» отчёта удержания.
Срок жизни идентификатора влияет на цифры. Короткий срок увеличивает число новых посетителей, длительный улучшает связывание возвращений и одновременно расширяет период наблюдения. Универсального правильного срока из SQL не следует. В нашей серии он остаётся продуктовым решением, которое владелец обязан задать до внешнего сбора.
Время действия и время доставки
Позднее завершение посетителя b произошло первого октября, но получено пятого. Отчёт по действиям должен отнести его к первому числу. Отчёт о работе сборщика должен отнести получение к пятому. Если использовать один столбец для обоих вопросов, либо история станет неверной, либо задержка доставки окажется невидимой.
Все учебные даты считаем в UTC. Для редактора может понадобиться календарный день другой зоны; тогда зона задаётся прямо в запросе и описании отчёта. Нельзя молча сравнивать календарные дни разных зон и объяснять расхождение «обновлением данных». Устойчивый временной контракт помогает разбирать пограничные события около полуночи.
Отрицательная задержка означает, что клиентское время позже серверного получения. Это не обязательно мистическая ошибка базы: часы устройства могли спешить. Такие строки стоит считать отдельно и помечать, а не превращать отрицательные значения в ноль без следа. Отчёт качества данных позже покажет подобные случаи.
Эволюция контракта
Новый тип события не должен менять смысл старого имени. Если потребуется измерять снятие закладки, можно ввести bookmark_removed и новую версию контракта. Старые отчёты продолжат понимать, что bookmark означал установку. Если вместо этого начать посылать противоположные действия под прежним именем, исторические сравнения потеряют смысл.
Каталог допустимых значений
Сборщик получает разрешённые имена событий и опубликованные идентификаторы уроков из согласованного списка. Строка сама по себе не доказывает существование материала. Ошибка в одной букве могла бы создать отдельную строку отчёта и скрыть часть просмотров настоящего урока.
Связь lesson_id с course_id тоже проверяется отдельно. Иначе один производитель отнесёт HTML к другому курсу, и общий счётчик останется правдоподобным, а фильтр курса потеряет строки. SQL-тип строки не предотвращает такого предметного расхождения.
Для фикстуры список мал и находится рядом с контрактом. Внешняя система нуждается в версионировании каталога и политике архивных материалов. Время публикации или удаления урока может определять, допустимо ли позднее событие по прежнему идентификатору.
Имена столбцов, допустимые события и правила времени сохранены в CONTRACT.md архива. Типы ClickHouse не проверяют весь предметный смысл: UUID может быть новым при повторе, а существующий lesson_id может относиться к чужому курсу. Следующий урок преобразует контракт в таблицу, оставив проверку этих связей на стороне сборщика и контроля качества.