diff --git a/docs/architecture/dds/README.md b/docs/architecture/dds/README.md index 8d37a5e..d0d7ab9 100644 --- a/docs/architecture/dds/README.md +++ b/docs/architecture/dds/README.md @@ -24,8 +24,8 @@ - **Сессия** (`dds.session`) — расчётная сущность: одна строка — визит, нарезку по правилу визита стенд делает сам, `VisitID` генератора — эталон сверки; наполнение заменой дневной партиции — [модель сессии](session.md). -- **Событие** (`dds.event_v`) — контракт над ODS, а не копия: язык - бизнес-модели событию даёт развилка #132. +- **Событие** (`dds.event_v`) — контракт над ODS, а не копия: представление, + переводящее событие на язык бизнес-модели, — [контракт события](event.md). - **Карта идентичностей** (`dds.identity_map`) — кука ↔ пользователь магазина; сборку решает развилка #133. @@ -49,8 +49,7 @@ ## Открытые решения -- Контракт `dds.event_v`, сборка `dds.identity_map` — развилки #132–#133 - карты #127. +- Сборка `dds.identity_map` — развилка #133 карты #127. - Запуск трансформаций и механика замены партиций на кластере — #134 и этап 5. - Витрины DM — #135. diff --git a/docs/architecture/dds/event.md b/docs/architecture/dds/event.md new file mode 100644 index 0000000..069a25f --- /dev/null +++ b/docs/architecture/dds/event.md @@ -0,0 +1,131 @@ +# Контракт события в DDS + +Учебный результат: менти видит первый на стенде пример правила «слой — это +контракт, а не обязательно копия данных». Заказ и сессия — физические +таблицы, а событие в DDS не хранится вовсе: `dds.event_v` — представление, +которое переводит событие на язык бизнес-модели, не копируя ни строки. +Витрины DM читают его, а не ODS напрямую. + +## Форма + +Обычное представление над `ods.event_v` — вторым уровнем, так что `FINAL` +остаётся спрятанным в один слой, внутри контракта ODS. Единственное +сомнение против такой формы — «представление материализуется до фильтра» — +снято исследованием [проталкивания +условий](../../research/2026-08-22-view-predicate-pushdown.md): условия +внешних запросов доезжают до партиций и первичного ключа сквозь два уровня +представлений и переименование колонок. + +Две оговорки исследования соблюдены контрактом. Ключевая для срезов колонка +`event_date` отдаётся переименованием `EventDate`, не пересчётом — иначе +индекс перестал бы её анализировать. Цена `FINAL` — отключённый +автоматический `PREWHERE` — свойство ODS, представление её не усугубляет. + +## Язык модели + +Имена атрибутов — имена модели, а не нормализованные имена источника +([CONTEXT.md](../../../CONTEXT.md), «Нормализованное имя»). Для большинства +колонок они совпадают: атрибут переезжает под нормализованным именем из +[описания выгрузки](../../formats/clickstream-event.md). Расхождения два, +и каждое — решение модели: + +- **`WatchID` → `event_id`**: модель зовёт сущность событием, «watch» — + жаргон выгрузки Метрики. +- **`UTCEventTime` → `event_time` в поясе счётчика** (`Europe/Samara`): + DDS обслуживает человека с дашбордом, время лежит местным ([дока + хранилища](../storage.md), «Часовые пояса»). Пересчёт — смена линзы, байты + те же, проталкиванию не мешает. Второй метки времени контракт не отдаёт: + выбор линзы слой сделал за читателя, UTC остаётся языком ODS. + +`ClientID` остаётся `client_id`: это устоявшееся имя куки во всей +экосистеме Метрики, по нему шардирование и мост к `dds.identity_map`; +своё имя здесь создало бы третий синоним. `client_timezone` тоже остаётся — +это атрибут посетителя, а не линза времени. + +## Расшифровки + +Одна: `device_category` — коды 1–4 в строки `desktop` / `mobile` / +`tablet` / `tv`. У настоящей Метрики этот атрибут и есть строка (выгрузка +стенда несёт число — принятое отступление: спека, раздел 1.1), контракт возвращает +ему боевой вид. + +Другие коды остаются кодами. `goals_reached` — справочника целей на стенде +нет, id и есть язык. Регионы в расшифровке не нуждаются: выгрузка несёт +названия рядом с id (`region_city`, `region_country`), словарь ничего бы не +раскрыл. Идея дать менти собрать справочник регионов самому ушла в лабы — +issue #143. + +## Что скрыто + +- **`VisitID`** — чужой суррогат сессии: свою сессию модель чеканит сама + (`session_id`, [модель сессии](session.md)), а эталон — инструмент + проверки, не атрибут модели. DQ-сверка нарезки идёт за эталоном в + `ods.event_v`: она по построению сопоставляет наш слой с чужим чеканом — + честно, что за чужим она и идёт в его слой. +- **`Sign`** — колонка формата без механики: генератор исправлений не шлёт, + и в модели события колонке ответить нечего. Честный рассказ о ней живёт в + DDL ODS и лекции. +- **`ecommerce`** — сырой JSON: сырьё, а не атрибут. Разобранные массивы + `purchase_*` и `product_*` в контракте есть; лаба про разбор JSON внутри + колонки читает ODS. +- **`_load_id`, `_load_ts`** — у представления нет собственной загрузки, а + чужие координаты слой не переносит (правило слоя — [модель + заказа](order.md), «Служебные колонки»); расследование спрашивает ODS по + ключу события. + +Остальные колонки отдаются целиком, включая `ip_address` и оба +`region_*_id`: контракт — не цензор, он прячет только то, чему в модели +нечего сказать. + +## Читатели + +- Сборка `dds.session`: ключи нарезки (`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` (пара «посетители / известные» через `identity_map`). +- Событийная сторона сверки `dm.purchase_vs_orders_v`: `purchase_id`, + `purchase_revenue`, `event_date`. +- DQ-сверка сессий с эталоном читает не его, а `ods.event_v` — + см. «Что скрыто». + +## Не входит + +- DDL представления и его раскатка на кластер — исполнение этапа 4, + нарезка #136. +- Форма витрин 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). Справочника целей нет; косметика без читателя. + +## Связанные решения + +- [Проталкивание условий сквозь + представления](../../research/2026-08-22-view-predicate-pushdown.md) — + резолюция #128: форма контракта и обе её оговорки. +- [Модель сессии](session.md) — `session_id` и довод против чужого чекана; + сюда же передан вопрос о судьбе `VisitID`. +- [Модель заказа](order.md) — правило служебных колонок слоя. +- Мастер-спека, раздел 7 — два контракта: `ods.event_v` хранит язык + источника, `dds.event_v` говорит языком модели. +- [CONTEXT.md](../../../CONTEXT.md) — «Нормализованное имя», «Хранилище». diff --git a/docs/architecture/dds/session.md b/docs/architecture/dds/session.md index dde6acb..d20211f 100644 --- a/docs/architecture/dds/session.md +++ b/docs/architecture/dds/session.md @@ -82,7 +82,7 @@ смотрят через `dds.event_v`; сессия и по смыслу не финальна, пока день не прожит. -Источник сборки — `dds.event_v`; его контракт решает развилка #132, сессии +Источник сборки — `dds.event_v` ([контракт события](event.md)): сессии нужны от него ключи нарезки, тип события, страница, UTM и паспорт куки. Механика запуска — где собирается партиция и как подменяется на кластере — не входит: это устройство трансформаций (#134, этап 5). @@ -95,9 +95,9 @@ компенсация ошибок внутри одной куки за день; эталон в модель не тащим — строка сессии остаётся нашей, а не генераторной. Поштучное сопоставление границ — материал лабы, где менти разбирает найденное расхождение руками. Дом проверки — -контур DQ этапа 4. Сверке нужен `VisitID`, а сборке сессий — нет; останется -ли он в контракте `dds.event_v` или проверка пойдёт за ним в ODS — решает -развилка #132. +контур DQ этапа 4. Сверке нужен `VisitID`, а сборке сессий — нет; контракт +`dds.event_v` его скрывает, и за эталоном проверка идёт в `ods.event_v` +([контракт события](event.md)). ## Читатели