feat(dds): модель сессии и нарезка визитов окнами #166

Merged
ddmitry merged 1 commits from 152-dds-session-model into main 2026-08-26 16:19:08 +03:00
Owner

Closes #152

Слой получил расчётную сущность: стенд режет визиты сам, а VisitID
генератора остаётся эталоном самопроверки.

Что внутри

  • dds.session_rep / _dist, двойник-донор dds.session_stage_rep / _dist
    и публичный договор dds.session_v.
  • Нарезка двумя оконными проходами из dds.event_v: первое окно ставит шов
    там, где пауза длиннее тридцати минут, второе считает нарастающий итог швов —
    номер визита внутри дня куки. Границу суток держит разбиение окна, отдельной
    проверки ей не нужно.
  • Объём прогона — от первой дыры в слое по последний прожитый день. Прожитый
    день выводится из позиции мира и первого дня событий: события живой день от
    прожитого не отличают, а собранная посреди дня сессия осталась бы обрезанной
    навсегда.
  • Группа задач session в dds_transform рядом с заказом, зависимости по
    данным между ними нет. Собирать нечего — группа пропускается, а не зеленеет
    впустую.

Приёмка

Мир пересобран с нуля (make rebuild-storage), даг собрал все восемь дней
стартового мира:

  • расхождение с uniqExact(VisitID) по паре «кука × день» — ноль;
  • 79 367 сессий, session_id уникальны, все 401 185 событий ровно в одной
    сессии каждое;
  • ни одной паузы длиннее тридцати минут внутри сессии и ни одной пары соседних
    визитов куки, разделённой меньшим промежутком;
  • повтор после DROP PARTITION ALL — те же session_id и то же содержимое
    строк;
  • снесённая руками партиция 2026-06-04: следующий прогон взял хвост от неё,
    дни до дыры не тронуты.

make lint, make smoke (21 проверка), make check-clickhouse (10),
make check-services — зелёные.

Ревью

Две слепые линии: Кодекс по коду, свежий Опус по уместности. Одиннадцать
находок — девять закрыты правкой, две отклонены с доводом и отклонение принято
на перепроверке обеими линиями. Самая ценная находка: два окна дают две
независимые сортировки, и при равных метках времени шов и нарастающий итог
могли достаться разным строкам, разорвав сессию надвое. Порядок в окнах теперь
полный — по метке времени и event_id.

Известная граница

Позиция мира отвечает за «прожит», а не за «доехал». Пока нет сторожа хвоста
топика hits (#156), недожёванный день замёрзнет недособранным: окна
пересборки у сессий нет. Сказано вслух в docs/architecture/etl.md и в
sql/dds/session_scope.sql.

Слияние рядом с #165

Ветка отрезана от main до слияния #165. Пересечься они могут в
docs/architecture/etl.md (раздел «Объём прогона», абзац про snapshot_date)
и в docs/architecture/storage.md. Кода это не касается: файлы ODS здесь не
трогались.

Closes #152 Слой получил расчётную сущность: стенд режет визиты сам, а `VisitID` генератора остаётся эталоном самопроверки. ## Что внутри - `dds.session_rep` / `_dist`, двойник-донор `dds.session_stage_rep` / `_dist` и публичный договор `dds.session_v`. - Нарезка двумя оконными проходами из `dds.event_v`: первое окно ставит шов там, где пауза длиннее тридцати минут, второе считает нарастающий итог швов — номер визита внутри дня куки. Границу суток держит разбиение окна, отдельной проверки ей не нужно. - Объём прогона — от первой дыры в слое по последний прожитый день. Прожитый день выводится из позиции мира и первого дня событий: события живой день от прожитого не отличают, а собранная посреди дня сессия осталась бы обрезанной навсегда. - Группа задач `session` в `dds_transform` рядом с заказом, зависимости по данным между ними нет. Собирать нечего — группа пропускается, а не зеленеет впустую. ## Приёмка Мир пересобран с нуля (`make rebuild-storage`), даг собрал все восемь дней стартового мира: - расхождение с `uniqExact(VisitID)` по паре «кука × день» — **ноль**; - 79 367 сессий, `session_id` уникальны, все 401 185 событий ровно в одной сессии каждое; - ни одной паузы длиннее тридцати минут внутри сессии и ни одной пары соседних визитов куки, разделённой меньшим промежутком; - повтор после `DROP PARTITION ALL` — те же `session_id` и то же содержимое строк; - снесённая руками партиция 2026-06-04: следующий прогон взял хвост от неё, дни до дыры не тронуты. `make lint`, `make smoke` (21 проверка), `make check-clickhouse` (10), `make check-services` — зелёные. ## Ревью Две слепые линии: Кодекс по коду, свежий Опус по уместности. Одиннадцать находок — девять закрыты правкой, две отклонены с доводом и отклонение принято на перепроверке обеими линиями. Самая ценная находка: два окна дают две независимые сортировки, и при равных метках времени шов и нарастающий итог могли достаться разным строкам, разорвав сессию надвое. Порядок в окнах теперь полный — по метке времени и `event_id`. ## Известная граница Позиция мира отвечает за «прожит», а не за «доехал». Пока нет сторожа хвоста топика `hits` (#156), недожёванный день замёрзнет недособранным: окна пересборки у сессий нет. Сказано вслух в `docs/architecture/etl.md` и в `sql/dds/session_scope.sql`. ## Слияние рядом с #165 Ветка отрезана от `main` до слияния #165. Пересечься они могут в `docs/architecture/etl.md` (раздел «Объём прогона», абзац про `snapshot_date`) и в `docs/architecture/storage.md`. Кода это не касается: файлы ODS здесь не трогались.
ddmitry added 1 commit 2026-08-26 16:12:17 +03:00
Зачем:
- Дать слою расчётную сущность, которой в потоке нет: стенд режет визиты сам,
  а VisitID генератора остаётся эталоном самопроверки.
- Отделить прожитый день от живого — иначе визит, начатый в 23:50, замёрз бы
  обрезанным навсегда: пересобирать замёрзший день слой не станет.

Что:
- Добавлены dds.session_rep/_dist, двойник-донор dds.session_stage_rep/_dist и
  публичный договор dds.session_v.
- Нарезка стоит на двух окнах: первое ставит шов там, где пауза длиннее
  тридцати минут, второе считает нарастающий итог швов — номер визита. Границу
  суток держит разбиение окна, отдельной проверки ей не нужно.
- Порядок в окнах полный, по метке времени и event_id: сортировок две, и при
  равных метках они разошлись бы, разорвав сессию надвое.
- Объём прогона считается от первой дыры в слое по последний прожитый день;
  прожитый день выводится из позиции мира и первого дня событий. Собирать
  нечего — группа задач пропускается, а не зеленеет впустую.
- В dds_transform появилась группа session рядом с заказом; зависимости по
  данным между ними нет.
- Потолок max_active_tasks: размноженная замена без него выносит планировщика
  за отведённые ему 640 МиБ.
- parallel_distributed_insert_select у сессий не назначается вслух — ключ
  шардирования у источника и цели один по контракту ко-локации.
- Записано, что позиция отвечает за «прожит», а не за «доехал»: сторож хвоста
  потока приезжает отдельным тикетом.

Проверка:
- make lint
- make smoke (21 проверка), make check-clickhouse (10), make check-services
- Стартовый мир, пересобранный с нуля: 79367 сессий за восемь дней;
  расхождение с uniqExact(VisitID) по паре «кука × день» — ноль; все 401185
  событий ровно в одной сессии; session_id уникальны.
- Ни одной паузы длиннее тридцати минут внутри сессии и ни одной пары соседних
  визитов куки, разделённой меньшим промежутком.
- Повтор после DROP PARTITION ALL: те же session_id и то же содержимое строк.
- Снесённая руками партиция 2026-06-04: следующий прогон взял хвост от неё,
  дни до дыры не тронуты.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ddmitry merged commit 650efacc3f into main 2026-08-26 16:19:08 +03:00
ddmitry deleted branch 152-dds-session-model 2026-08-26 16:19:09 +03:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ddmitry/clickstream-data-platform#166