Зачем: выражения «собираются в донор» и «публикуются дни» требовали знать устройство конвейера до чтения объяснения. Что: описаны назначение промежуточных таблиц и последовательность расчета, записи и замены разделов. Перечитаны остальные объяснения: выбор дней, разворачивание позиций заказа, проверка сессий и повтор запуска. Проверка: make lint, git diff --check, локальные ссылки и якоря. SQL без комментариев и AST обоих дагов без строк документации не изменились. Refs: #249
14 KiB
Модель сессии в 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:
session_scope.sqlнаходит первую дату событий, за которую еще нет сессий. Последний день для расчета — первый день событий ODS плюсworld_positionминус один;world_position— номер следующего дня для генерации. Так в расчет попадают только полностью прожитые дни. При пустом ODS запрос не возвращает строк и задача падает; если пропущенных прожитых дней нет,first_dayравенNULLи группа пропускается.session_rebuild.sqlочищает промежуточную таблицуdds.session_stage_rep. Затем рассчитывает сессии выбранных дней и записывает строки черезdds.session_stage_dist. Пока идет расчет, в основной таблице остаются прежние данные. Вставка ждет доставки строк на шарды благодаряdistributed_foreground_insert = 1.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 — «Визит», «Граница суток», «День активности».