Files
ddadmin 9394b789e5 docs(labs): исправлены две ошибки согласования
- Зачем: после замены терминов осталось неверное согласование слов.
- Что: исправлены обороты «узла, который выполнил» и «число ... не изменилось».
- Проверка: проверены diff, сохранность SQL и команд, таблиц состава колонок и локальных ссылок; git diff --check прошел.
2026-10-18 18:16:26 +02:00

194 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Расчет сессий в стенде
На прошлой ступени вы рассчитали сессии отдельным запросом. Теперь
перепишете расчет внутри стенда, запустите его через Airflow и проверите
результат по исходным событиям.
## Подготовка
- [задание «Сессии из событий»](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` передаст
параметры и выполнит запросы по порядку. Таблицы уже созданы: основные хранят
готовые сессии, промежуточные нужны для нового расчета перед заменой данных.
Запрос `sessions_vs_reference.sql` сравнивает число сессий с `VisitID` из ODS.
## Выполнение запросов в Airflow
Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл.
| Файл | Вход | Результат |
|---|---|---|
| `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` |
`first_day` должен быть первым завершившимся модельным днем, которого еще
нет в DDS, а `last_day` последним завершившимся днем. Начальную дату возьмите
из событий ODS: `position` считает дни от нее. Текущий модельный день еще
не закончен, поэтому сессии за него не рассчитываются.
Если в ODS нет ни одного события, запрос не возвращает строк. Если все
завершившиеся дни уже есть в DDS, запрос возвращает строку с `first_day = NULL`.
Тогда Airflow пропускает группу задач расчета сессий: ее состояние будет `skipped`.
`session_rebuild.sql` сначала очищает `dds.session_stage_rep` на всем кластере.
Затем он записывает в `dds.session_stage_dist` расчет за все выбранные дни. Добавьте к
вставке `SETTINGS distributed_foreground_insert = 1`. Так строки успеют попасть
на шарды до замены партиций.
Запрос `session_replace.sql` заменяет партицию в основной `dds.session_rep`
готовой партицией из промежуточной `dds.session_stage_rep`.
Операция выполняется на всем кластере, на локальных таблицах каждого шарда.
Не ставьте точку с запятой в конце `session_scope.sql`. Драйвер добавляет формат
ответа. С точкой с запятой получится несколько команд, которые ClickHouse
отклонит. День для замены подставляет шаблон Jinja в Airflow. Параметр
ClickHouse в `ALTER ... PARTITION` здесь не работает.
## Правила расчета сессий
Источник сборки — `dds.event_v`. Одна строка результата представляет один
визит. На третьей ступени вы уже получили правила разделения событий на сессии:
- сессия не пересекает модельные сутки;
- пауза больше 30 минут открывает новую сессию;
- в расчете участвуют события всех типов.
Повторный расчет должен давать тот же результат. При одинаковом времени
упорядочивайте события по `event_id`: порядок в оконных функциях должен быть
`(event_time, event_id)`. Один визит должен получать одинаковый `session_id`
при каждом запуске.
Вычислите идентификатор как `cityHash64(client_id, min(event_time))`.
Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и
длительность. Посчитайте все события, просмотры, добавления в корзину и
покупки. Возьмите входную и выходную страницы, UTM первого события, устройство
и город. Заполните также служебные колонки из схемы таблицы.
Пишите через `dds.session_stage_dist`: шард для строки определяется по
`client_id`. При записи прямо в локальную таблицу он зависел бы от узла,
который выполнил запрос.
## Запуск нового расчета
Готовые дни повторно не собираются. Чтобы новый SQL точно выполнился, выберите
последний собранный день:
```sql
SELECT max(session_date)
FROM dds.session_v;
```
Берите последний день: если удалить более ранний, расчет придется повторить
и за все последующие завершившиеся дни.
Сохраните десять строк с идентификатором, границами, длительностью и счетчиками:
```sql
SELECT
session_id,
client_id,
session_date,
started_at,
finished_at,
duration_seconds,
events_total,
pageviews,
cart_adds,
purchases
FROM dds.session_v
WHERE session_date = 'YYYY-MM-DD'
ORDER BY client_id, started_at, session_id
LIMIT 10;
```
Создайте в DBeaver подключение к первому узлу по [инструкции из
README](../../README.md#подключиться-из-dbeaver). Укажите пользователя `etl` и
пароль `CLICKHOUSE_ETL_PASSWORD` из `.env`. У этой учетной записи есть право
менять таблицы DDS на кластере. Подставьте найденную дату и удалите только ее
партицию:
```sql
ALTER TABLE dds.session_rep ON CLUSTER clickstream_cluster
DROP PARTITION 'YYYY-MM-DD';
```
Откройте Airflow и запустите `etl_pipeline`. В даге `dds_transform` раскройте
группу `session`: в ней должны выполниться задачи `scope` и `rebuild`, а также
`replace` для выбранного дня. Если после третьей ступени мир ушел вперед,
в группе появятся задачи `replace` и для последующих прожитых дней. Следом
обновятся витрины и выполнится `dq_check`.
Если прогон дошел до замены, но упал на проверке, ошибочная партиция уже
находится в DDS. После правки снова удалите тот же день перед следующим
запуском. Иначе день останется заполненным, а новая сборка для него не
выполнится.
## Проверка результата
Сначала повторите запрос с десятью строками. `session_id`, границы,
длительность и счетчики должны совпасть с сохраненным результатом.
Это проверит, что ваш расчет восстановил те же сессии с теми же идентификаторами.
Задача `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;
```
Эта сверка сравнивает число сессий каждого `client_id` за день с `VisitID`.
При `failed_rows = 0` число сессий совпало, но их границы, страницы и UTM
еще могут различаться. Как на третьей ступени, проверьте отдельные сессии.
Для одного `client_id` выбранного дня найдите начало и конец каждого
`VisitID` по событиям и сравните с границами строк `dds.session_v`.
По исходным событиям проверьте страницы, UTM, устройство
и город. Страницы и UTM не входят в сохраненные десять строк: готовый SQL
стенда выбирал их через `argMin` и `argMax` только по `event_time`. При равном
времени ожидаемое значение определяйте по исходным событиям и полному порядку
`(event_time, event_id)`.
Проверьте также, на каком шарде лежит каждая строка. Напишите отдельный
запрос к `dds.session_dist`. Он должен:
1. получить фактический узел строки через `hostName()`;
2. определить ожидаемый узел по `cityHash64(client_id)`;
3. посчитать строки, у которых фактический и ожидаемый узлы не совпали.
У кластера два равных шарда: остаток 0 соответствует `clickhouse-01`, остаток
1 — `clickhouse-02`. Сгруппируйте ответ по фактическому узлу. На каждом узле
число строк на чужом шарде должно быть нулевым. Проверка числа сессий читает
Distributed-таблицу и такую ошибку не заметит.
После зеленого результата запустите `etl_pipeline` еще раз, не удаляя
партицию. Группа сессий должна быть пропущена: все завершившиеся дни уже
рассчитаны, новых дней и пропусков нет.
## Результат работы
- [ ] вы переписали три файла `sql/dds/session_*.sql`, сохранив даг,
таблицы и проверку стенда;
- [ ] ваш расчет восстановил удаленный день, `etl_pipeline` завершился успешно;
- [ ] вы сверили десять сохраненных строк: `session_id`, границы,
длительность и счетчики совпали после пересборки;
- [ ] вы получили ноль расхождений в `sessions_vs_reference` за все
прожитые дни и вручную сверили границы и поля сессии одного `client_id`;
- [ ] ваш запрос с `hostName()` не нашел строк на чужом шарде;
- [ ] повторный прогон без удаления данных пропустил группу сессий.