Files
clickstream-data-platform/docs/architecture/dds/event.md
T
ddadmin ae5bd31dbd docs(dds): уточнён публичный контракт события
- Зачем:
  - Закрепить реалистичную границу доступа BI и убрать двусмысленность для неизвестных кодов.
- Что:
  - Пояснены доступ к ODS, пояс счётчика и порядок DDL.
  - Проверка равенства строк отделена от размера конкретной загрузки.
- Проверка:
  - `git diff --check`.
  - `make config-test` не стартовал: Snap требует работающий `snapd.apparmor`.
2026-08-25 17:53:22 -04:00

10 KiB

Контракт события в DDS

Учебный результат: менти видит первый на стенде пример правила «слой — это контракт, а не обязательно копия данных». Заказ и сессия — физические таблицы, а событие в DDS не хранится вовсе: dds.event_v — представление, которое переводит событие на язык бизнес-модели, не копируя ни строки. Витрины DM читают его, а не ODS напрямую.

Форма

Обычное представление над ods.event_v — вторым уровнем, так что FINAL остаётся спрятанным в один слой, внутри контракта ODS. Единственное сомнение против такой формы — «представление материализуется до фильтра» — снято исследованием проталкивания условий: условия внешних запросов доезжают до партиций и первичного ключа сквозь два уровня представлений и переименование колонок.

Две оговорки исследования соблюдены контрактом. Ключевая для срезов колонка event_date отдаётся переименованием EventDate, не пересчётом — иначе индекс перестал бы её анализировать. Цена FINAL — отключённый автоматический PREWHERE — свойство ODS, представление её не усугубляет.

Как публичный договор DDS, представление перечисляет колонки явно и следует общей конвенции чтения, «Имена объектов».

Язык модели

Имена атрибутов — имена модели, а не нормализованные имена источника (CONTEXT.md, «Нормализованное имя»). Для большинства колонок они совпадают: атрибут переезжает под нормализованным именем из описания выгрузки. Расхождения два, и каждое — решение модели:

  • WatchID → event_id: модель зовёт сущность событием, «watch» — жаргон выгрузки Метрики.
  • UTCEventTime → event_time в поясе счётчика (Europe/Samara): DDS обслуживает человека с дашбордом, время лежит местным (дока хранилища, «Часовые пояса»). Пересчёт — смена линзы, байты те же, проталкиванию не мешает. Второй метки времени контракт не отдаёт: выбор линзы слой сделал за читателя, UTC остаётся языком ODS.

ClientID остаётся client_id: это устоявшееся имя куки во всей экосистеме Метрики, по нему шардирование и мост к dds.identity_map_v; своё имя здесь создало бы третий синоним. client_timezone тоже остаётся — это атрибут посетителя, а не линза времени.

Расшифровки

Одна: device_category — коды 1–4 в строки desktop / mobile / tablet / tv. У настоящей Метрики этот атрибут и есть строка (выгрузка стенда несёт число — принятое отступление: спека, раздел 1.1), контракт возвращает ему боевой вид.

Значения device_category 1–4 получают имена, остальные сохраняются десятичной строкой. Новый код остаётся видимым, пока у него не появится имя в договоре.

Остальные кодированные атрибуты не расшифровываются. Для goals_reached справочника целей на стенде нет, id и есть язык. Регионы в расшифровке не нуждаются: выгрузка несёт названия рядом с id (region_city, region_country), словарь ничего бы не раскрыл. Идея дать менти собрать справочник регионов самому ушла в лабы — issue #143.

Что скрыто

  • VisitID — чужой суррогат сессии: свою сессию модель чеканит сама (session_id, модель сессии), а эталон — инструмент проверки, не атрибут модели. DQ-сверка нарезки идёт за эталоном в ods.event_v: она по построению сопоставляет наш слой с чужим чеканом — честно, что за чужим она и идёт в его слой.
  • Sign — колонка формата без механики: генератор исправлений не шлёт, и в модели события колонке ответить нечего. Честный рассказ о ней живёт в DDL ODS и лекции.
  • ecommerce — сырой JSON: сырьё, а не атрибут. Разобранные массивы purchase_* и product_* в контракте есть; лаба про разбор JSON внутри колонки читает ODS.
  • _load_ts — у представления нет собственной загрузки, а чужие координаты слой не переносит (правило слоя — модель заказа, «Служебные колонки»); расследование спрашивает ODS по ключу события.

Остальные колонки отдаются целиком, включая ip_address и оба region_*_id: контракт — не цензор, он прячет только то, чему в модели нечего сказать.

Читатели

  • Сборка сессий: ключи нарезки (client_id, event_time, event_date), тип события, страница, UTM, паспорт куки — категория устройства приходит уже расшифрованной.
  • Витрины dm.events_enriched_v, dm.top_pages_daily_v, dm.utm_effectiveness_v (declared_revenue — из purchase_revenue), dm.daily_traffic_v (пара «посетители / известные» через dds.identity_map_v).
  • Событийная сторона сверки dm.purchase_vs_orders_v: purchase_id, purchase_revenue, event_date.
  • DQ-сверка сессий с эталоном читает не его, а ods.event_v — см. «Что скрыто».

Не входит

  • Форма витрин DM — #135.
  • Лаба со справочником регионов — issue #143, проектирование лаб.

Отклонённые варианты

  • Материализация вместо представления. Дублировала бы широкий ODS ради переименований, и урок «слой — контракт, а не копия» пропал бы; единственный довод за — сомнение в проталкивании условий — снят исследованием #128.
  • utc_event_time второй меткой рядом с event_time. Контракт вернул бы читателю выбор линзы, который слой обязан делать сам; расхождение двух часов — урок ODS, не DDS.
  • visit_id в контракте. Сверка и лаба читали бы одну поверхность, но чужой чекан встал бы атрибутом модели — тем же доводом модель сессии не пустила его в свою строку. Решение владельца: DQ-опыт показательнее, когда эталон приходит из другого слоя.
  • cookie_id вместо client_id. Честное имя модели, но третий синоним куки на ровном месте: проза говорит «кука», экосистема — ClientID.
  • Словарь регионов на стенде. Повторил бы урок dictGet каталога вторым разом, а раскрывать нечего — названия уже в строке. Идея «сделать руками» ушла в лабы (issue #143).
  • Прочие расшифровки (goals_reached в имена целей, has_gclid в Bool). Справочника целей нет; косметика без читателя.

Связанные решения