# Модель сессии в DDS Учебный результат: менти строит расчётную сущность, которой нет в потоке, — режет события на сессии по правилу визита, чеканит идентификатор и сверяет свою нарезку с эталоном. Стенд здесь играет роль сессионизатора: в бою её играет Метрика, которая режет визиты у себя и с октября 2025 отдаёт готовый `VisitID` прямо в хитах. Поэтому стенд считает сессии сам, а `VisitID` генератора использует только как эталон самопроверки — иначе сборка сессий выродилась бы в `GROUP BY` по чужому полю и центральный урок слоя пропал бы. Без этого абзаца действия стенда выглядят странно: «зачем считать то, что уже дано колонкой». ## Зерно Одна строка — один визит ([CONTEXT.md](../../../CONTEXT.md)): подряд идущие события одной куки без пауз длиннее 30 минут, не пересекающие границу суток. В нарезку входят все три типа событий — торговые садятся на свои страницы и держат сессию наравне с просмотрами. События внутри сессии упорядочиваются по метке времени события (`UTCEventTime` — опорная колонка приёма). Граница суток проходит по модельному дню счётчика, и он у события уже есть: `EventDate` — свойство содержимого. Раз сессия сутки не пересекает, у всех её событий `EventDate` один; он и становится **днём сессии** — колонкой партиции, без пересчёта. ## Идентификатор Идентификатор сессии в природе не существует: сессия — расчётная сущность, и id ей чеканит тот, кто сессионизирует. `VisitID` — такой же суррогат, отчеканенный генератором в роли Метрики; наш — `session_id`, детерминированный хеш от пары «кука + начало сессии» (`cityHash64`). Детерминированность обязательна: партиция дня пересобирается заменой, и повторный прогон обязан отчеканить те же id — счётчик или UUID это правило ломают. `session_id` — не ключ сортировки и не ключ шардирования, а колонка-ручка: в соединениях он не участвует — события его не несут, мост к заказам живёт на событиях. Читатели ручки — лаба сессий (сопоставить свою нарезку со стендовой и назвать расхождение одним числом) и разбор DQ-расхождений. ## Состав строки Боевой образец — таблица визитов Метрики, парная к нашему формату хита; по ней состав и собран: - **ключи и времена**: кука, `session_id`, начало и конец сессии, длительность, день сессии. Времена — в поясе счётчика по правилу слоя; - **счётчики**: событий всего, просмотров, корзин, покупок; - **страницы**: входная и выходная — как `StartURL`/`EndURL` визита; полного списка страниц нет и у Метрики, топы считаются по событиям; - **атрибуция**: UTM-метки первого события сессии — так визит атрибутирует Метрика. В v1 витрина брала последнее касание — заменено осознанно; - **паспорт куки**: категория устройства (расшифрованная, язык бизнес-модели) и город — у куки они постоянны, берутся с первого события; - **служебные**: `_load_id` и `_load_ts` собравшего запуска — правило слоя ([модель заказа](order.md), «Служебные колонки»). Осознанные расхождения с образцом: **денег в сессии нет** — визит Метрики несёт клиентскую выручку, у нас деньги считает только бэкенд (спека, раздел 7), а клиентская сумма живёт в сверке; **целей нет** — их место заняли счётчики торговых событий. ## Движок и наполнение `dds.session_rep` — `ReplicatedMergeTree` без колонки версии, по образцу заказа: партиция — день сессии, ключ сортировки — кука и начало сессии. `dds.session_dist` шардирует по `cityHash64(client_id)`, тем же выражением, что события, — это ко-локация с `dds.identity_map_rep` ([дока хранилища](../storage.md), «Раскладка по шардам»). Совпадение ключа с событиями не случайно: все события куки лежат на одном шарде, поэтому сессионизация локальна по природе — капкан смены ключа при вставке, который дока хранилища обещает сборкам DDS, у сессий не стреляет. Сборка — дневной батч: хранилище выше ODS по смыслу батчевое, раз второй источник приходит раз в сутки. Собирается день, который мир уже прожил, и собирается один раз и целиком; какой день прожит, слой спрашивает у позиции мира ([дневной конвейер](../etl.md), «Объём прогона»). Идемпотентность держит замена партиции. В отличие от заказов, где батч пересобирает до K «дышащих» дней, у сессии дышащего окна нет: прожитый день самодостаточен (правило спеки, раздел 6). Второй раз он соберётся только вынужденно — когда упавшая замена оставила позади дыру и хвост от неё накрыл соседние дни. Дней в одном прогоне при этом бывает несколько — столько, сколько мир успел прожить без слоя. Внутри живого дня модель сессии про сегодня честно молчит — «сегодня» смотрят через `dds.event_v`; сессия и по смыслу не финальна, пока день не прожит. Публичная поверхность — `dds.session_v`: обычное представление с явным списком деловых и собственных служебных колонок над `dds.session_dist`. Роли объектов и доступ следуют общей [конвенции](../storage.md), «Имена объектов». Источник сборки — `dds.event_v` ([контракт события](event.md)): сессии нужны от него ключи нарезки, тип события, страница, UTM и паспорт куки. Механика запуска — где собирается партиция и как подменяется на кластере — не входит: это устройство [дневного конвейера](../etl.md). ## Сверка с эталоном Счётная DQ-проверка с зерном «кука × день»: число сессий нашей нарезки равно `uniqExact(VisitID)` по тем же событиям. Счёт по Distributed ловит и склеенную, и разрезанную сессию — мимо него проходит лишь взаимная компенсация ошибок внутри одной куки за день; эталон в модель не тащим — строка сессии остаётся нашей, а не генераторной. Поштучное сопоставление границ — материал лабы, где менти разбирает найденное расхождение руками. Дом проверки — контур DQ этапа 4. Сверке нужен `VisitID`, а сборке сессий — нет; контракт `dds.event_v` его скрывает, и за эталоном проверка идёт в `ods.event_v` ([контракт события](event.md)). ## Читатели - Витрина обзора сессий `session_overview_v`: дневные итоги визитов в срезе устройств; читает `dds.session_v` и поднимает зерно до дня — состав и довод в ее [ТЗ](../../labs/1-3-session-overview.md) (#135). - DQ-сверка с эталоном (см. выше). - Лаба сессий: менти собирает нарезку сам до знакомства с кодом стенда, потом сравнивает — с эталоном и со стендовой. ## Не входит - Механика запуска трансформации и замены партиций на кластере — [дневной конвейер](../etl.md). - Контур проверок качества (даг DQ, `dm.dq_summary`) — отдельный разговор этапа 4. - Форма витрин над сессиями — #135. ## Отклонённые варианты - **Готовый `VisitID` вместо своей нарезки.** Сборка выродилась бы в `GROUP BY` по чужому полю: центральный урок слоя съеден, сверка с эталоном осталась бы без предмета. Боевой довод «Метрика теперь отдаёт `VisitID` в хитах» честно рассказывается в лабе — после того как менти собрал сам. - **Гибрид: сборка по `VisitID`, DQ пересчитывает нарезку.** Сторож на нарезку, которой сборка не делает; та же работа, что в варианте «считать сам», но урок спрятан в проверку. - **Одно динамическое представление вместо физической таблицы.** Оконная сессионизация на каждое чтение; урок «слой — контракт, а не копия» уже закреплён за `dds.event_v` — доводы модели заказа, здесь они только сильнее. Публичное `dds.session_v` поверх готовой физической модели ничего заново не считает. - **Счётчик или UUID вместо детерминированного хеша.** Пересборка партиции выдала бы другие id — замена перестала бы быть идемпотентной. - **`VisitID` колонкой в строке сессии.** Чужой чекан в своей модели: наша сессия обязана уметь расходиться с эталоном, на этом стоит сверка; поштучное сопоставление — лаба, не таблица. - **Массив посещённых страниц.** Вес без читателя: вход и выход — как у визита Метрики, топы страниц считает витрина по событиям. - **UTM последнего касания (как в v1).** Визит атрибутируется своим первым хитом; последнее касание — атрибуция другого уровня, не сессионного. - **Деньги в сессии.** Выручка — только по бэкенду; клиентская сумма живёт в сверке (спека, разделы 4 и 7). ## Связанные решения - [Модель заказа](order.md) — прецедент замены партиций в DDS и правило служебных колонок слоя. - [Дока хранилища](../storage.md) — раскладка по шардам, конвенции имён, часовые пояса. - Мастер-спека, разделы 1.2 и 7 — `VisitID` как эталон самопроверки (принятое отступление приёмки), карта таблиц. - [CONTEXT.md](../../../CONTEXT.md) — «Визит», «Граница суток», «День активности».