Files
clickstream-data-platform/docs/architecture/dds/session.md
T
ddadmin 086007fbd4 docs(warehouse): объяснены расчет строк и замена дневных разделов
Зачем: выражения «собираются в донор» и «публикуются дни» требовали
знать устройство конвейера до чтения объяснения.

Что: описаны назначение промежуточных таблиц и последовательность
расчета, записи и замены разделов. Перечитаны остальные объяснения:
выбор дней, разворачивание позиций заказа, проверка сессий и повтор запуска.

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

Refs: #249
2026-09-13 15:16:26 -04:00

14 KiB
Raw Blame History

Модель сессии в DDS

Сессия объединяет события одного визита. На стенде вы учитесь определять ее границы по событиям, вычислять устойчивый идентификатор и сверять результат с эталоном. Поэтому сборка читает dds.event_v, где нет готового VisitID. Этот идентификатор источника остается в ODS для проверки.

Зерно

Зерно описывает, чему соответствует одна строка: здесь это один визит. В него входят подряд идущие события одного 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.

В сессии нет суммы выручки: ее считают по заказам магазина. Клиентская сумма покупки используется в сверке с заказами и в учебной UTM-витрине.

Движок и наполнение

dds.session_rep хранит готовые сессии в ReplicatedMergeTree без колонки версии. При чтении не нужно заново выполнять оконный расчет. Партиция — раздел таблицы за один session_date; ключ сортировки — посетитель и начало сессии. Читайте результат через dds.session_v: представление над dds.session_dist явно перечисляет деловые и служебные колонки.

Источник сборки — dds.event_v. События, сессии и карта идентичностей распределены по одному ключу cityHash64(client_id): данные посетителя находятся на одном шарде. Роли объектов и правило записи через _dist описаны в справочнике хранилища.

Три 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, их набор на стенде больше не меняется. Поэтому готовые сессии обычно не пересчитываются. Если пропущен ранний день, диапазон включает и все последующие прожитые дни, даже уже собранные. Если за день сохранилась хотя бы одна сессия, поиск пропущенных дней не обнаружит потерю остальных. Условия повторного запуска и замены разделов описаны в справочнике конвейера.

Сессий еще идущего дня в модели нет; его события доступны через dds.event_v. Перед выбором дней даг ждет завершения доставки событий в ODS — см. готовность источника.

Сверка с эталоном

Проверка 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 и поднимает зерно до дня — состав и довод в ее ТЗ (#135).
  • DQ-сверка с эталоном (см. выше).
  • Лаба сессий: менти собирает нарезку сам до знакомства с кодом стенда, потом сравнивает — с эталоном и со стендовой.

Не входит

  • Механика запуска трансформации и замены партиций на кластере — дневной конвейер.
  • Контур проверок качества (даг 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).

Связанные решения

  • Модель заказа — прецедент замены партиций в DDS и правило служебных колонок слоя.
  • Дока хранилища — раскладка по шардам, конвенции имён, часовые пояса.
  • Мастер-спека, разделы 1.2 и 7 — VisitID как эталон самопроверки (принятое отступление приёмки), карта таблиц.
  • CONTEXT.md — «Визит», «Граница суток», «День активности».