docs(dds): спроектирована модель сессии — нарезка стендом, эталон VisitID
Зачем: развилка #131 карты #127 — зерно и правила сборки dds.session; резолюция переносится в доки слоя тем же PR. Что: дока docs/architecture/dds/session.md — зерно «один визит», нарезка по правилу визита самим стендом (стенд в роли сессионизатора), session_id как детерминированный хеш, состав строки по образцу визита Метрики без денег и целей, MergeTree с заменой дневной партиции, сборка одним дневным батчем после слепка, счётная DQ-сверка с VisitID (кука × день); в README слоя сессия переведена из открытых решений в карту модели. Проверка: вычитка доки; ссылки на разделы спеки и соседние доки сверены с текстом. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
111315990b
commit
6f2692737b
2 files changed
+151
-3
No files matched your search
@@ -21,7 +21,9 @@
|
||||
- **Заказ** (`dds.order`) — физическая сущность: одна строка — заказ в
|
||||
текущем состоянии, позиции массивами в строке, наполняется заменой
|
||||
партиций — [модель заказа](order.md).
|
||||
- **Сессия** (`dds.session`) — зерно и правила сборки решает развилка #131.
|
||||
- **Сессия** (`dds.session`) — расчётная сущность: одна строка — визит,
|
||||
нарезку по правилу визита стенд делает сам, `VisitID` генератора — эталон
|
||||
сверки; наполнение заменой дневной партиции — [модель сессии](session.md).
|
||||
- **Событие** (`dds.event_v`) — контракт над ODS, а не копия: язык
|
||||
бизнес-модели событию даёт развилка #132.
|
||||
- **Карта идентичностей** (`dds.identity_map`) — кука ↔ пользователь
|
||||
@@ -47,8 +49,8 @@
|
||||
|
||||
## Открытые решения
|
||||
|
||||
- Сессии, контракт `dds.event_v`, сборка `dds.identity_map` — развилки
|
||||
#131–#133 карты #127.
|
||||
- Контракт `dds.event_v`, сборка `dds.identity_map` — развилки #132–#133
|
||||
карты #127.
|
||||
- Запуск трансформаций и механика замены партиций на кластере — #134 и
|
||||
этап 5.
|
||||
- Витрины DM — #135.
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Модель сессии в DDS
|
||||
|
||||
Учебный результат: менти строит расчётную сущность, которой нет в потоке, —
|
||||
режет события на сессии по правилу визита, чеканит идентификатор и сверяет
|
||||
свою нарезку с эталоном. Стенд здесь играет роль сессионизатора: в бою её
|
||||
играет Метрика, которая режет визиты у себя и с октября 2025 отдаёт готовый
|
||||
`VisitID` прямо в хитах. Поэтому стенд считает сессии сам, а `VisitID`
|
||||
генератора использует только как эталон самопроверки — иначе сборка
|
||||
`dds.session` выродилась бы в `GROUP BY` по чужому полю и центральный урок
|
||||
слоя пропал бы. Без этого абзаца действия стенда выглядят странно: «зачем
|
||||
считать то, что уже дано колонкой».
|
||||
|
||||
## Зерно
|
||||
|
||||
Одна строка — один визит ([CONTEXT.md](../../../CONTEXT.md)): подряд идущие
|
||||
события одной куки без пауз длиннее 30 минут, не пересекающие границу суток.
|
||||
В нарезку входят все три типа событий — торговые сидят на своих страницах и
|
||||
рвут или начинают сессию наравне с просмотрами. События внутри сессии
|
||||
упорядочиваются по метке времени события — той же, по которой упорядочен
|
||||
ключ сортировки ODS.
|
||||
|
||||
Граница суток проходит по модельному дню счётчика, и он у события уже есть:
|
||||
`EventDate` — свойство содержимого. Раз сессия сутки не пересекает, у всех
|
||||
её событий `EventDate` один; он и становится **днём сессии** — колонкой
|
||||
партиции, без пересчёта.
|
||||
|
||||
## Идентификатор
|
||||
|
||||
Идентификатор сессии в природе не существует: сессия — расчётная сущность,
|
||||
и id ей чеканит тот, кто сессионизирует. `VisitID` — такой же суррогат,
|
||||
отчеканенный генератором в роли Метрики; наш — `session_id`,
|
||||
детерминированный хеш от пары «кука + начало сессии» (`cityHash64`).
|
||||
Детерминированность обязательна: партиция дня пересобирается заменой, и
|
||||
повторный прогон обязан отчеканить те же id — счётчик или UUID это правило
|
||||
ломают.
|
||||
|
||||
`session_id` — не ключ сортировки и не ключ шардирования, а колонка-ручка:
|
||||
витрины сессий читают таблицу напрямую, их зерно совпадает с зерном модели,
|
||||
и в соединениях id не участвует — события его не несут, мост к заказам живёт
|
||||
на событиях. Читатели ручки — лаба сессий (сопоставить свою нарезку со
|
||||
стендовой и назвать расхождение одним числом) и разбор DQ-расхождений.
|
||||
|
||||
## Состав строки
|
||||
|
||||
Боевой образец — таблица визитов Метрики, парная к нашему формату хита; по
|
||||
ней состав и собран:
|
||||
|
||||
- **ключи и времена**: кука, `session_id`, начало и конец сессии,
|
||||
длительность, день сессии. Времена — в поясе счётчика по правилу слоя;
|
||||
- **счётчики**: событий всего, просмотров, корзин, покупок — при таксономии
|
||||
из трёх типов это и есть «цели» стенда;
|
||||
- **страницы**: входная и выходная — как `StartURL`/`EndURL` визита; полного
|
||||
списка страниц нет и у Метрики, топы считаются по событиям;
|
||||
- **атрибуция**: UTM-метки первого события сессии — так визит атрибутирует
|
||||
Метрика. В v1 витрина брала последнее касание — заменено осознанно;
|
||||
- **паспорт куки**: категория устройства (расшифрованная, язык бизнес-модели)
|
||||
и город — у куки они постоянны, берутся с первого события;
|
||||
- **служебные**: `_load_id` и `_load_ts` собравшего запуска — правило слоя
|
||||
([модель заказа](order.md), «Служебные колонки»).
|
||||
|
||||
Осознанные расхождения с образцом: **денег в сессии нет** — визит Метрики
|
||||
несёт клиентскую выручку, у нас деньги считает только бэкенд (спека, раздел
|
||||
7), а клиентская сумма живёт в сверке; **целей нет** — их место заняли
|
||||
счётчики торговых событий.
|
||||
|
||||
## Движок и наполнение
|
||||
|
||||
`MergeTree` без колонки версии, по образцу заказа: партиция — день сессии,
|
||||
ключ сортировки — кука и начало сессии, шардирование распределённой таблицы —
|
||||
`cityHash64(ClientID)`, тем же выражением, что у событий, — ко-локация с
|
||||
`dds.identity_map` ([дока хранилища](../storage.md), «Раскладка по шардам»).
|
||||
|
||||
Сборка — дневной батч после прихода слепка заказов: хранилище выше ODS по
|
||||
смыслу батчевое, раз второй источник приходит раз в сутки. К этому моменту
|
||||
события дня полны, и сессии только что прожитого дня собираются один раз и
|
||||
целиком; идемпотентность держит замена партиции. В отличие от заказов, где
|
||||
батч пересобирает до K «дышащих» дней, у сессий пересобирается только одна
|
||||
партиция: день самодостаточен и замерзает сразу (правило спеки, раздел 6).
|
||||
Внутри живого дня `dds.session` про сегодня честно молчит — «сегодня»
|
||||
смотрят через `dds.event_v`; сессия и по смыслу не финальна, пока день не
|
||||
прожит.
|
||||
|
||||
Источник сборки — `dds.event_v`; его контракт решает развилка #132, сессии
|
||||
нужны от него ключи нарезки, тип события, страница, UTM и паспорт куки.
|
||||
Механика запуска — где собирается партиция и как подменяется на кластере —
|
||||
не входит: это устройство трансформаций (#134, этап 5).
|
||||
|
||||
## Сверка с эталоном
|
||||
|
||||
Счётная DQ-проверка с зерном «кука × день»: число сессий нашей нарезки
|
||||
равно `uniqExact(VisitID)` по тем же событиям. Счёт по Distributed ловит и
|
||||
склеенную, и разрезанную сессию; эталон в модель не тащим — строка сессии
|
||||
остаётся нашей, а не генераторной. Поштучное сопоставление границ — материал
|
||||
лабы, где менти разбирает найденное расхождение руками. Дом проверки —
|
||||
контур DQ этапа 4.
|
||||
|
||||
## Читатели
|
||||
|
||||
- Витрина обзора сессий `session_overview_v`: длительности, глубина,
|
||||
вход/выход; читает таблицу напрямую, зерно совпадает (#135).
|
||||
- DQ-сверка с эталоном (см. выше).
|
||||
- Лаба сессий: менти собирает нарезку сам до знакомства с кодом стенда,
|
||||
потом сравнивает — с эталоном и со стендовой.
|
||||
|
||||
## Не входит
|
||||
|
||||
- Механика запуска трансформации и замены партиций на кластере — #134 и
|
||||
этап 5.
|
||||
- Контур проверок качества (даг DQ, `dm.dq_summary`) — отдельный разговор
|
||||
этапа 4.
|
||||
- Форма витрин над сессиями — #135.
|
||||
|
||||
## Отклонённые варианты
|
||||
|
||||
- **Готовый `VisitID` вместо своей нарезки.** Сборка выродилась бы в
|
||||
`GROUP BY` по чужому полю: центральный урок слоя съеден, сверка с эталоном
|
||||
осталась бы без предмета. Боевой довод «Метрика теперь отдаёт `VisitID` в
|
||||
хитах» честно рассказывается в лабе — после того как менти собрал сам.
|
||||
- **Гибрид: сборка по `VisitID`, DQ пересчитывает нарезку.** Сторож на
|
||||
нарезку, которой сборка не делает; та же работа, что в варианте «считать
|
||||
сам», но урок спрятан в проверку.
|
||||
- **Представление вместо таблицы.** Оконная сессионизация на каждое чтение;
|
||||
урок «слой — контракт, а не копия» уже закреплён за `dds.event_v` — доводы
|
||||
модели заказа, здесь они только сильнее.
|
||||
- **Счётчик или UUID вместо детерминированного хеша.** Пересборка партиции
|
||||
выдала бы другие id — замена перестала бы быть идемпотентной.
|
||||
- **`VisitID` колонкой в строке сессии.** Чужой чекан в своей модели: наша
|
||||
сессия обязана уметь расходиться с эталоном, на этом стоит сверка;
|
||||
поштучное сопоставление — лаба, не таблица.
|
||||
- **Массив посещённых страниц.** Вес без читателя: вход и выход — как у
|
||||
визита Метрики, топы страниц считает витрина по событиям.
|
||||
- **UTM последнего касания (как в v1).** Визит атрибутируется своим первым
|
||||
хитом; последнее касание — атрибуция другого уровня, не сессионного.
|
||||
- **Деньги в сессии.** Выручка — только по бэкенду; клиентская сумма живёт
|
||||
в сверке (спека, разделы 4 и 7).
|
||||
|
||||
## Связанные решения
|
||||
|
||||
- [Модель заказа](order.md) — прецедент замены партиций в DDS и правило
|
||||
служебных колонок слоя.
|
||||
- [Дока хранилища](../storage.md) — раскладка по шардам, конвенции имён,
|
||||
часовые пояса.
|
||||
- Мастер-спека, разделы 1.2 и 7 — `VisitID` как эталон самопроверки
|
||||
(принятое отступление приёмки), карта таблиц.
|
||||
- [CONTEXT.md](../../../CONTEXT.md) — «Визит», «Граница суток», «День
|
||||
активности».
|
||||
Reference in new issue
Block a user