Files
clickstream-data-platform/docs/architecture/dds/session.md
T
ddadminandClaude Fable 5 92ed3011e6 docs(dds): спроектирован контракт dds.event_v — язык модели поверх ODS
- Зачем:
  - развилка #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 <noreply@anthropic.com>
2026-08-24 14:04:51 -04:00

152 lines
13 KiB
Markdown

# Модель сессии в DDS
Учебный результат: менти строит расчётную сущность, которой нет в потоке, —
режет события на сессии по правилу визита, чеканит идентификатор и сверяет
свою нарезку с эталоном. Стенд здесь играет роль сессионизатора: в бою её
играет Метрика, которая режет визиты у себя и с октября 2025 отдаёт готовый
`VisitID` прямо в хитах. Поэтому стенд считает сессии сам, а `VisitID`
генератора использует только как эталон самопроверки — иначе сборка
`dds.session` выродилась бы в `GROUP BY` по чужому полю и центральный урок
слоя пропал бы. Без этого абзаца действия стенда выглядят странно: «зачем
считать то, что уже дано колонкой».
## Зерно
Одна строка — один визит ([CONTEXT.md](../../../CONTEXT.md)): подряд идущие
события одной куки без пауз длиннее 30 минут, не пересекающие границу суток.
В нарезку входят все три типа событий — торговые садятся на свои страницы
и держат сессию наравне с просмотрами. События внутри сессии
упорядочиваются по метке времени события (`UTCEventTime` — опорная колонка
приёма).
Граница суток проходит по модельному дню счётчика, и он у события уже есть:
`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), «Раскладка по шардам»).
Совпадение ключа с событиями не случайно: все события куки лежат на одном
шарде, поэтому сессионизация локальна по природе — капкан смены ключа при
вставке, который дока хранилища обещает сборкам DDS, у сессий не стреляет.
Сборка — дневной батч после прихода слепка заказов: хранилище выше ODS по
смыслу батчевое, раз второй источник приходит раз в сутки. К этому моменту
события дня полны, и сессии только что прожитого дня собираются один раз и
целиком; идемпотентность держит замена партиции. В отличие от заказов, где
батч пересобирает до K «дышащих» дней, у сессий пересобирается только одна
партиция: день самодостаточен и замерзает сразу (правило спеки, раздел 6).
Внутри живого дня `dds.session` про сегодня честно молчит — «сегодня»
смотрят через `dds.event_v`; сессия и по смыслу не финальна, пока день не
прожит.
Источник сборки — `dds.event_v` ([контракт события](event.md)): сессии
нужны от него ключи нарезки, тип события, страница, UTM и паспорт куки.
Механика запуска — где собирается партиция и как подменяется на кластере —
не входит: это устройство трансформаций (#134, этап 5).
## Сверка с эталоном
Счётная DQ-проверка с зерном «кука × день»: число сессий нашей нарезки
равно `uniqExact(VisitID)` по тем же событиям. Счёт по Distributed ловит и
склеенную, и разрезанную сессию — мимо него проходит лишь взаимная
компенсация ошибок внутри одной куки за день; эталон в модель не тащим —
строка сессии остаётся нашей, а не генераторной. Поштучное сопоставление
границ — материал лабы, где менти разбирает найденное расхождение руками. Дом проверки —
контур DQ этапа 4. Сверке нужен `VisitID`, а сборке сессий — нет; контракт
`dds.event_v` его скрывает, и за эталоном проверка идёт в `ods.event_v`
([контракт события](event.md)).
## Читатели
- Витрина обзора сессий `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) — «Визит», «Граница суток», «День
активности».