Files
clickstream-data-platform/docs/architecture/dds/session.md
T
ddadmin 5f317ba8d2 docs(warehouse): упрощены объяснения витрин, сессий и пересчета
Зачем: читателю ступеней 1–4 нужно проследить выбор дней, расчет строки
и публикацию результата без расшифровки метафор.

Что: согласованы справочники DM, сессий, ETL и два раздела хранилища
с комментариями шести SQL-примеров. Уточнены ограничения повторного
запуска и выбора атрибутов при равном времени событий.

Проверка: make lint, git diff --check, локальные Markdown-ссылки и якоря.
SQL без комментариев совпадает; AST Python без строк документации совпадает.
Исторические разделы и материалы вне границ задачи сохранены.

Refs: #249
2026-09-13 14:33:39 -04:00

165 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Модель сессии в DDS
Сессия объединяет события одного визита. На стенде вы учитесь определять
ее границы по событиям, вычислять устойчивый идентификатор и сверять
результат с эталоном. Поэтому сборка читает `dds.event_v`, где нет готового
`VisitID`. Этот идентификатор источника остается в ODS для проверки.
## Зерно
Зерно описывает, чему соответствует одна строка: здесь это один
[визит](../../../CONTEXT.md#люди-и-события). В него входят подряд идущие
события одного `client_id` без пауз длиннее 30 минут и без перехода через
модельную полночь. Пауза ровно в 30 минут новую сессию не открывает.
Учитываются все три типа событий: `pageview`, `add_to_cart`, `purchase`.
День сессии `session_date` берется из `event_date` событий DDS — это
`EventDate` источника, дата по часовому поясу счетчика. У всех событий
одной сессии она одинакова. Пересчитывать ее из времени UTC не нужно.
Оба окна нарезки используют порядок `(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` минус один. Так в расчет попадают только полностью
прожитые дни. При пустом 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` партицией
донора. Для каждого дня Airflow создает отдельную задачу `replace`.
В обычном ходе сессии прожитого дня больше не меняются. Если пропущен ранний
день, диапазон включает и все последующие прожитые дни, даже уже собранные.
Поиск по наличию даты не обнаружит неполный день, у которого часть строк
сохранилась. Условия повторного запуска и границы атомарности замены
описаны в [справочнике конвейера](../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) — «Визит», «Граница суток», «День
активности».