# Модель сессии в DDS Сессия объединяет события одного визита. На стенде вы учитесь определять ее границы по событиям, вычислять устойчивый идентификатор и сверять результат с эталоном. Поэтому сборка читает `dds.event_v`, где нет готового `VisitID`. Этот идентификатор источника остается в ODS для проверки. ## Зерно Зерно описывает, чему соответствует одна строка: здесь это один [визит](../../../CONTEXT.md#люди-и-события). В него входят подряд идущие события одного `client_id` без пауз длиннее 30 минут и без перехода через модельную полночь. Пауза ровно в 30 минут новую сессию не открывает. Учитываются все три типа событий: `pageview`, `add_to_cart`, `purchase`. День сессии `session_date` берется из `event_date` событий DDS — это `EventDate` источника, дата по часовому поясу счетчика. У всех событий одной сессии она одинакова. Пересчитывать ее из времени UTC не нужно. SQL рассчитывает границы визитов двумя оконными функциями: первая отмечает начало нового визита, вторая по этим отметкам присваивает событиям номер визита. Обе используют порядок `(event_time, event_id)`: при равном времени события идут по идентификатору. Иначе два расчета могли бы по-разному упорядочить события и отнести отметку начала к разным строкам. ## Идентификатор `session_id` вычисляется как `cityHash64(client_id, min(event_time))`. При повторной сборке тех же событий посетитель и начало визита остаются прежними, поэтому идентификатор не меняется. Счетчик или случайный UUID выдали бы другие значения. По `session_id` можно сопоставить сессии до и после пересборки и назвать конкретную строку при разборе расхождения. Он не используется для сортировки или шардирования. У событий этого поля нет; связь с заказами строится по событиям покупки. ## Состав строки - `client_id`, `session_id`, `session_date` — посетитель, сессия и ее день. - `started_at`, `finished_at`, `duration_seconds` — время первого и последнего события и разница между ними в секундах. Времена хранятся в часовом поясе счетчика. - `events_total`, `pageviews`, `cart_adds`, `purchases` — число всех событий и счетчики трех типов. - `entry_url`, `exit_url` — страницы первого и последнего события. - `utm_*` — метки первого события: по ним визит относят к рекламной кампании. - `device_category`, `region_city` — устройство и город посетителя. На стенде они постоянны для одной куки. - `_load_id`, `_load_ts` — идентификатор запуска и время сборки строки. В готовом SQL страницы выбираются через `argMin` и `argMax`, а UTM, устройство и город — через `argMin`, только по `event_time`. При одинаковом времени выбор между событиями не определен. Полный порядок окон нарезки сам по себе эту неоднозначность не устраняет. Это ограничение учитывается в [задании ступени 4](../../labs/4-1-session-pipeline.md). В сессии нет суммы выручки: ее считают по заказам магазина. Клиентская сумма покупки используется в сверке с заказами и в учебной UTM-витрине. ## Движок и наполнение `dds.session_rep` хранит готовые сессии в `ReplicatedMergeTree` без колонки версии. При чтении не нужно заново выполнять оконный расчет. Партиция — раздел таблицы за один `session_date`; ключ сортировки — посетитель и начало сессии. Читайте результат через `dds.session_v`: представление над `dds.session_dist` явно перечисляет деловые и служебные колонки. Источник сборки — `dds.event_v`. События, сессии и карта идентичностей распределены по одному ключу `cityHash64(client_id)`: данные посетителя находятся на одном шарде. Роли объектов и правило записи через `_dist` описаны в [справочнике хранилища](../storage.md#раскладка-по-шардам). Три SQL-файла выполняются последовательно в группе `session` дага `dds_transform`: 1. `session_scope.sql` находит первую дату событий, за которую еще нет сессий. Последний день для расчета — первый день событий ODS плюс `world_position` минус один; `world_position` — номер следующего дня для генерации. Так в расчет попадают только полностью прожитые дни. При пустом ODS запрос не возвращает строк и задача падает; если пропущенных прожитых дней нет, `first_day` равен `NULL` и группа пропускается. 2. `session_rebuild.sql` очищает промежуточную таблицу `dds.session_stage_rep`. Затем рассчитывает сессии выбранных дней и записывает строки через `dds.session_stage_dist`. Пока идет расчет, в основной таблице остаются прежние данные. Вставка ждет доставки строк на шарды благодаря `distributed_foreground_insert = 1`. 3. `session_replace.sql` заменяет в `dds.session_rep` раздел за каждый выбранный день готовым разделом `dds.session_stage_rep`. Для каждого дня Airflow создает отдельную задачу `replace`. Когда все события завершенного дня приняты в ODS, их набор на стенде больше не меняется. Поэтому готовые сессии обычно не пересчитываются. Если пропущен ранний день, диапазон включает и все последующие прожитые дни, даже уже собранные. Если за день сохранилась хотя бы одна сессия, поиск пропущенных дней не обнаружит потерю остальных. Условия повторного запуска и замены разделов описаны в [справочнике конвейера](../etl.md#идемпотентность). Сессий еще идущего дня в модели нет; его события доступны через `dds.event_v`. Перед выбором дней даг ждет завершения доставки событий в ODS — см. [готовность источника](../etl.md#готовность-источника). ## Сверка с эталоном Проверка `sessions_vs_reference` в `dq_check` сравнивает для каждого посетителя и дня число строк `dds.session_v` с `uniqExact(VisitID)` из `ods.event_v`. Эталон читается из ODS, потому что `dds.event_v` скрывает `VisitID`. Нулевое число расхождений подтверждает только число сессий каждого посетителя за день. Например, ошибочное объединение двух визитов и разделение другого на два сохранят их общее число. Поэтому в заданиях вы дополнительно сопоставляете границы сессий по исходным событиям и проверяете страницы, UTM, устройство и город. ## Читатели - Витрина обзора сессий `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) — «Визит», «Граница суток», «День активности».