# Перепиши нарезку стенда **Чему учит:** изменить расчетную сущность внутри готового конвейера и проверить результат внешним эталоном. ## Сначала прочитать - [задание «Нарежь сессии сам»](3-1-sessions-by-hand.md); - [модель сессии DDS](../architecture/dds/session.md); - [дневной конвейер трансформаций](../architecture/etl.md#объём-прогона); - [раскладка по шардам](../architecture/storage.md#раскладка-по-шардам). ## Граница задания Стенд уже умеет запускать трансформацию, хранить сессии и проверять их число. Перепишите только три файла: - `sql/dds/session_scope.sql`; - `sql/dds/session_rebuild.sql`; - `sql/dds/session_replace.sql`. Удалите из них готовый SQL и соберите свою реализацию. Даг `dds_transform` передаст ей параметры и запустит запросы в нужном порядке. DDL уже создает целевые таблицы и донор. Донор — служебная таблица, где день собирается перед заменой. Запрос `sessions_vs_reference.sql` проверит опубликованный результат. ## Договор с дагом Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл. | Файл | Вход | Результат | |---|---|---| | `session_scope.sql` | `{position:UInt16}` — номер первого несыгранного дня | `first_day` и `last_day` | | `session_rebuild.sql` | `{first_day:Date}`, `{last_day:Date}`, `{load_id:String}` | Собранные сессии в `dds.session_stage_dist` | | `session_replace.sql` | День в `{{ params.day }}` | Партиция дня в `dds.session_rep` | `session_scope.sql` ищет первый прожитый день, которого еще нет в DDS. От него до последнего прожитого дня получается непрерывный отрезок. Начало шкалы нужно взять из событий ODS: `position` считает дни от него. Живой модельный день еще не закончен, поэтому сессии за него не собираются. Если в ODS нет ни одного события, запрос не возвращает строк. Если DDS уже догнал мир, запрос возвращает строку с `first_day = NULL`. Даг поймет, что работы нет, и пропустит группу сессий. `session_rebuild.sql` сначала очищает `dds.session_stage_rep` на всем кластере. Затем он собирает в `dds.session_stage_dist` все дни отрезка. Вставка должна закончиться до замены партиций. `session_replace.sql` заменяет один день. Цель — локальная таблица `dds.session_rep`, источник — локальный донор `dds.session_stage_rep`. Операция выполняется на всем кластере. `session_scope.sql` читается как один запрос, поэтому в конце ему не нужна точка с запятой. День для замены подставляет шаблон Jinja в Airflow. Параметр ClickHouse в `ALTER ... PARTITION` здесь не работает. ## Нарежьте сессии Источник сборки — `dds.event_v`. Одна строка результата представляет один визит. Используйте правила, которые уточнили на третьей ступени: - сессия не пересекает модельные сутки; - пауза больше 30 минут открывает новую сессию; - в нарезке участвуют события всех типов; - при одинаковом времени порядок событий задает `event_id`, поэтому полный порядок окон — `(event_time, event_id)`; - `session_id` детерминирован и зависит от куки и начала сессии. Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и длительность. Посчитайте все события, просмотры, добавления в корзину и покупки. Возьмите входную и выходную страницы, UTM первого события, устройство и город. Служебные колонки тоже входят в договор таблицы. Пишите через `dds.session_stage_dist`. Эта таблица разложит строки по ключу донора. ## Запустите свою реализацию Готовые дни повторно не собираются. Чтобы новый SQL точно выполнился, выберите последний собранный день: ```sql SELECT max(session_date) FROM dds.session_v; ``` Сохраните десять строк этого дня без служебных колонок загрузки: ```sql SELECT * EXCEPT (_load_id, _load_ts) FROM dds.session_v WHERE session_date = 'YYYY-MM-DD' ORDER BY client_id, started_at LIMIT 10; ``` Подставьте найденную дату и удалите только ее партицию. Команда идет под служебным пользователем `etl`, потому что `analyst` имеет доступ только на чтение: ```bash docker compose exec -T clickhouse-01 sh -c \ 'clickhouse-client --user etl --password "$CLICKHOUSE_ETL_PASSWORD" --query "$1"' \ _ "ALTER TABLE dds.session_rep ON CLUSTER clickstream_cluster DROP PARTITION 'YYYY-MM-DD'" ``` Откройте Airflow и запустите `etl_pipeline`. В даге `dds_transform` раскройте группу `session`: в ней должны выполниться задачи `scope` и `rebuild`, а также `replace` для выбранного дня. Если после третьей ступени мир ушел вперед, Airflow создаст `replace` и для последующих прожитых дней. Следом конвейер запустит витрины и `dq_check`. Если прогон дошел до замены, но упал на проверке, ошибочная партиция уже находится в DDS. После правки снова удалите тот же день перед следующим запуском. Иначе расчет объема сочтет день готовым и пропустит вашу новую сборку. ## Проверьте результат Сначала повторите запрос с десятью строками. `session_id` и все деловые колонки должны совпасть с сохраненным результатом. `_load_id` и `_load_ts` изменятся: день собрал другой запуск. Задача `dq_check.sessions_vs_reference` должна стать зеленой. Та же проверка видна в хранилище: ```sql SELECT data_date, reference_rows, actual_rows, failed_rows, status FROM dm.dq_summary_v WHERE check_name = 'sessions_vs_reference' ORDER BY data_date; ``` Эта сверка сравнивает число сессий каждой куки за день с `VisitID`. Нулевой `failed_rows` не подтверждает границы, страницы и UTM: две ошибки могут оставить тот же счет. Возьмите поштучную сверку из третьей ступени. Для одной куки выбранного дня сопоставьте границы `VisitID` по событиям с границами строк `dds.session_v`. Проверьте начало, конец и атрибуты сессии. Осталась физическая раскладка. Напишите отдельный запрос к `dds.session_dist`. Для каждой строки он должен: 1. получить фактическую ноду через `hostName()`; 2. пересчитать ожидаемую ноду по `cityHash64(client_id)`; 3. посчитать строки, у которых ноды не совпали. У кластера два равных шарда: остаток 0 соответствует `clickhouse-01`, остаток 1 — `clickhouse-02`. Сгруппируйте ответ по фактической ноде. На каждой ноде число строк на чужом шарде должно быть нулевым. Счетная DQ-проверка читает Distributed-таблицу и такую ошибку не заметит. После зеленого результата запустите `etl_pipeline` еще раз, не удаляя партицию. Расчет объема должен пропустить группу сессий: новых прожитых дней и дыр в слое нет. ## Приемка - [ ] переписаны только три файла `sql/dds/session_*.sql`, инфраструктура упражнения не изменена; - [ ] адресный прогон восстановил выбранный день и завершил `etl_pipeline` успешно; - [ ] сохраненные `session_id` и деловые колонки десяти строк совпали после пересборки; - [ ] `sessions_vs_reference` дает ноль расхождений во всех прожитых днях; - [ ] для одной куки вручную сверены границы и атрибуты сессии; - [ ] пересчет ключа по `hostName()` не нашел строк на чужом шарде; - [ ] повторный прогон без новой дыры пропустил группу сессий.