Сессии: dds.session и нарезка визитов #152
Notifications
Due Date
No due date set.
Blocks
Depends on
#154 Витрины стенда: даг dm_transform, revenue_daily и daily_traffic
ddmitry/clickstream-data-platform
#150 Контракт события: представление dds.event_v
ddmitry/clickstream-data-platform
#151 Модель заказа в DDS: dds.order и пересборка окна
ddmitry/clickstream-data-platform
Reference: ddmitry/clickstream-data-platform#152
Reference in new issue
Block a user
Part of #6.
Цель
Расчётная сущность слоя: нарезка событий на визиты правилом стенда,
детерминированный
session_id, дневной батч хвоста несобранных днейзаменой партиций. Учебный результат (session.md): менти строит сущность,
которой нет в потоке, — и её можно сверить с эталоном.
Что войдёт
dds.session_rep— партиция«день сессии», ключ сортировки — кука и начало сессии;
dds.session_dist— шардированиеcityHash64(client_id)(ко-локация ссобытиями и картой);
dds.session_v; грантbi_readerна_v.dds.event_vпо правилу визита (CONTEXT.md); составстроки — по session.md;
session_id— детерминированныйcityHash64от пары «кука + начало сессии»: пересборка партиции чеканит те же id.
dds_transformрядом с заказами (зависимости данныхмежду ними нет): хвост от последнего собранного дня до последнего
прожитого, сессии живого дня не собираются; двойник и
REPLACE PARTITION— по образцу заказа.Границы
VisitID— тикет контура DQ; эталон в модель нетащить.
Кому что
DDL и SQL нарезки — Opus: оконная сессионизация — центральный урок слоя.
Codex — опыты по брифу: повтор прогона, счётные свойства, уборка за
опытами.
Сначала прочитать
Проверка
make lint; на стендеmake smoke,make check-clickhouseиmake check-services(в даг пришла новая задача) зелёные.dds.session_vпротивdds.event_v: усессии одна кука и один день; паузы внутри ≤ 30 минут; соседние сессии
куки разделены паузой > 30 минут или границей суток.
«кука × день» равно
uniqExact(VisitID)изods.event_v—расхождение нулевое.
session_id.Уточнения к постановке по итогам разбора с владельцем. Тело тикета не правим — читать вместе с ним.
1. Объём прогона: чем определяется «последний прожитый день»
События не отличают живой день от прожитого: и посреди дня D, и сразу после него
max(event_date) = D. А спека требует различать — «сессии только что прожитого дня собираются один раз и целиком», но «внутри живого дня модель про сегодня честно молчит».Два признака проверены и отклонены:
snapshot --day Dотправляет слепок дня D−1 (player.play_snapshots,cli.py), в описи мира это 7 слепков на 8 дней старта. После проигрыша дня D в ODSmax(event_date) = D, аmax(snapshot_date) = D−1. Сверх того признак завёл бы сессиям зависимость от заказов, которой тикет обещает не иметь.Решение — позиция мира плюс якорь в данных:
Позиция ставится ровно в нужный момент: последним шагом работника и только по успеху (
world_live_day,Variable.set(WORLD_POSITION, str(first_day + 1))). Читать её — как вworld_control.first_unplayed_day:Variable.getи внятная ошибка на отсутствие или мусор.Якорь не требует второй копии физики мира.
EventDateдня N — константаORIGIN + Nна весь день (generator/.../day.py), уods.eventнет TTL (он только у*_errors), аPARTITION BY EventDateотдаёт минимум по метаданным партиций.Сходится на трёх положениях:
world_initialize(дни 0…7)Это и есть формулировка резолюции карты в
etl.md: «объём выводится изworld_positionи состояния слоёв». Доводetl.mdпротив позиции целил в другой вопрос — «какой день доехал», и для окна заказов он в силе. Здесь спрашивается «какой день прожит», и отвечает на это только позиция.За «доехал» позиция не отвечает. Если хвост
hitsне дожёван, формула назовёт день, чьи события ещё в пути, и день замёрзнет недособранным — окна пересборки у сессий нет. Сторож (нулевое отставание потребителей) приезжает с #156; на приёмке #152 отставание проверяется руками перед счётной сверкой.Форма передачи. В запрос объёма позиция едет параметром ClickHouse (
{position:UInt16}), какwindow_daysу заказа. Jinja нужна только вALTER … PARTITION: туда сервер параметры не подставляет (замерено,sql/dds/order_replace.sql).2. Левый край хвоста — первый недостающий день
Формулировка «хвост от последнего собранного дня» приехала из
etl.mdи написана до заказа.sql/dds/order_scope.sqlрешает наоборот и объясняет почему: замены партиций размножены по дням и падают порознь, поэтому упавший день оставляет дыру, а счёт по максимуму собранного дня её не заметил бы — прогоны продолжали бы зеленеть над щербатым слоем. У сессий устройство то же, а цена выше: дышащего окна, которое затянуло бы дыру само, здесь нет.Значит,
GLOBAL NOT INпо дням (обе таблицы распределённые) — как у заказа. Запрос объёма читаетods.event_distбезFINAL: дубли доставки минимум и максимум даты не двигают. Формулировку вetl.mdпоправить тем же PR.3. Двойник — в список DDL
В «Что войдёт» названы
session_rep,session_dist,session_v, а двойник только подразумевается следующим пунктом. Нужныdds.session_stage_repиdds.session_stage_dist:REPLACE PARTITIONберёт готовый кусок из другой таблицы, результат запроса ей не скормишь, а сборка прямо в цель удвоила бы день при повторе. Операция требует совпадения структуры, ключа партиции, сортировки, первичного ключа и политики хранения (отсюдаAS dds.session_repвместо повторённого списка колонок), а пошардовая замена — ещё и совпадения ключа шардирования. СвязкаDROP PARTITION+INSERTдвойник не заменяет: между шагами день пуст для читателя.4. Грант
bi_reader— делать нечегоУ роли уже
GRANT SELECT ON dds.*(infra/clickhouse/users.d/access.xml), а по ADR 0007 представление — договор чтения, не граница прав. Объектных грантов в репозитории нет ни у одного объекта. Пункт читать как «ничего не требуется», новыйGRANTне добавлять.5. Мелочь для решения в коде
parallel_distributed_insert_select: заказ ставит0явно и объясняет, что совпадение ключей у него случайное. У сессий ключ источника и цели совпадает по контракту ко-локации, то есть заводская2тоже верна. Выбор сделать вслух и записать комментарием, а не оставить умолчанию.