From 92ed3011e6a5005708444e677f2cf34b1b475e39 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 24 Aug 2026 14:04:51 -0400 Subject: [PATCH 1/2] =?UTF-8?q?docs(dds):=20=D1=81=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=82=D1=80=D0=B0=D0=BA=D1=82=20dds.event=5Fv=20?= =?UTF-8?q?=E2=80=94=20=D1=8F=D0=B7=D1=8B=D0=BA=20=D0=BC=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BF=D0=BE=D0=B2=D0=B5=D1=80=D1=85=20ODS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - развилка #132 карты #127: витринам DM и сборке сессий нужен контракт события на языке бизнес-модели, первый пример правила «слой — это контракт, а не обязательно копия данных». - Что: - создан docs/architecture/dds/event.md: представление над ods.event_v, имена модели (event_id, event_time в поясе счётчика, event_date переименованием), расшифровка device_category, скрыты VisitID, Sign, ecommerce и служебные колонки. - в README карта модели ведёт на event.md, развилка убрана из открытых решений; session.md ссылается на резолюцию судьбы VisitID. - Проверка: - вычитка; относительные ссылки в docs/architecture/dds/ ведут на существующие файлы. Co-Authored-By: Claude Fable 5 --- docs/architecture/dds/README.md | 7 +- docs/architecture/dds/event.md | 132 +++++++++++++++++++++++++++++++ docs/architecture/dds/session.md | 8 +- 3 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 docs/architecture/dds/event.md 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..4a19fa6 --- /dev/null +++ b/docs/architecture/dds/event.md @@ -0,0 +1,132 @@ +# Контракт события в 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. +- **`EventDate` → `event_date`** — переименованием, см. «Форма». + +`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`, `top_pages_daily_v`, + `utm_effectiveness_v` (`declared_revenue` — из `purchase_revenue`), + `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)). ## Читатели -- 2.54.0 From 7fc12fb8c95a4c6cf6e112168ccc84ab1009ae28 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 24 Aug 2026 14:09:15 -0400 Subject: [PATCH 2/2] =?UTF-8?q?docs(dds):=20=D0=B2=D1=8B=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - горячее ревью резолюции #132 нашло шероховатости, мешающие читателю. - Что: - срезан дубль про event_date из «Языка модели» — факт живёт в «Форме», расхождений имён осталось два. - выровнены префиксы dm. в «Читателях», распутана фраза про два слоя DQ-сверки, ссылка на спеку — в домашнем стиле. - Проверка: - вычитка. Co-Authored-By: Claude Fable 5 --- docs/architecture/dds/event.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/architecture/dds/event.md b/docs/architecture/dds/event.md index 4a19fa6..069a25f 100644 --- a/docs/architecture/dds/event.md +++ b/docs/architecture/dds/event.md @@ -26,7 +26,7 @@ Имена атрибутов — имена модели, а не нормализованные имена источника ([CONTEXT.md](../../../CONTEXT.md), «Нормализованное имя»). Для большинства колонок они совпадают: атрибут переезжает под нормализованным именем из -[описания выгрузки](../../formats/clickstream-event.md). Расхождений три, +[описания выгрузки](../../formats/clickstream-event.md). Расхождения два, и каждое — решение модели: - **`WatchID` → `event_id`**: модель зовёт сущность событием, «watch» — @@ -36,7 +36,6 @@ хранилища](../storage.md), «Часовые пояса»). Пересчёт — смена линзы, байты те же, проталкиванию не мешает. Второй метки времени контракт не отдаёт: выбор линзы слой сделал за читателя, UTC остаётся языком ODS. -- **`EventDate` → `event_date`** — переименованием, см. «Форма». `ClientID` остаётся `client_id`: это устоявшееся имя куки во всей экосистеме Метрики, по нему шардирование и мост к `dds.identity_map`; @@ -47,7 +46,7 @@ Одна: `device_category` — коды 1–4 в строки `desktop` / `mobile` / `tablet` / `tv`. У настоящей Метрики этот атрибут и есть строка (выгрузка -стенда несёт число — принятое отступление, спека 1.1), контракт возвращает +стенда несёт число — принятое отступление: спека, раздел 1.1), контракт возвращает ему боевой вид. Другие коды остаются кодами. `goals_reached` — справочника целей на стенде @@ -61,8 +60,8 @@ issue #143. - **`VisitID`** — чужой суррогат сессии: свою сессию модель чеканит сама (`session_id`, [модель сессии](session.md)), а эталон — инструмент проверки, не атрибут модели. DQ-сверка нарезки идёт за эталоном в - `ods.event_v`: она по построению сопоставляет наш слой с чужим чеканом, - и честно, что за его сторону она ходит в его слой. + `ods.event_v`: она по построению сопоставляет наш слой с чужим чеканом — + честно, что за чужим она и идёт в его слой. - **`Sign`** — колонка формата без механики: генератор исправлений не шлёт, и в модели события колонке ответить нечего. Честный рассказ о ней живёт в DDL ODS и лекции. @@ -83,9 +82,9 @@ issue #143. - Сборка `dds.session`: ключи нарезки (`client_id`, `event_time`, `event_date`), тип события, страница, UTM, паспорт куки — категория устройства приходит уже расшифрованной. -- Витрины `dm.events_enriched_v`, `top_pages_daily_v`, - `utm_effectiveness_v` (`declared_revenue` — из `purchase_revenue`), - `daily_traffic_v` (пара «посетители / известные» через `identity_map`). +- Витрины `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` — -- 2.54.0