Files
clickstream-data-platform/docs/labs/6-1-event-parsing.md
T
ddadmin 3bfe9ca4ee docs(labs): уточнены самопроверка и формулировки заданий
Зачем: убрать механические повторы в итогах и избежать ложных расхождений при проверке графика Superset.

Что: уточнены правила итоговых списков и название задания о проверках заказов. В разборе событий разделены учет повторных доставок и выбор событий ODS для сравнения. Первый график Superset и проверочный SQL используют весь период витрины.

Проверка: git diff --check, локальные ссылки и переходы к измененным заголовкам прошли. Блоки кода и таблицы требований сохранены. Согласованность периода графика с SQL проверена по тексту; повторный проход через интерфейс Superset не выполнялся.
2026-09-13 11:20:03 -04:00

220 lines
17 KiB
Markdown
Raw 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.
# Разберите сырые события рядом со стендом
**Чему учит:** пакетно разобрать сообщения отдельно от живого приема и
построчно проверить свой результат по нетронутому ODS.
## Сначала прочитать
- [формат события](../formats/clickstream-event.md);
- [справочник хранилища](../architecture/storage.md), разделы про имена
объектов, актуальные события, прием потока и срок жизни сырых данных;
- [ADR о приеме событий](../adr/0005-event-ingestion.md), раздел с принятым
решением;
- схему [`ods.event_rep`](../../sql/ddl/20-ods-tables.sql).
Готовый стендовый разбор находится в `sql/ddl/30-ods-views.sql`. Откройте его
после собственной сверки: в нем лежит ответ на основную часть задания.
## Что предстоит сделать
Создайте в базе `default` одну свою таблицу годных событий и наполните ее одним
пакетным `INSERT SELECT` из `stg.hits_raw_dist`. Работайте с первой нодой под
пользователем `etl`.
Матвью `ods.event_mv` и `ods.event_errors_mv` продолжают принимать живой
поток. Оставьте их, STG, ODS, Kafka и даги без изменений. Пересоздание матвью
на работающем стенде оставило бы зазор в приеме, а для пакетного опыта оно не
нужно.
Дайте своей таблице имя, которое не пересечется с чужой работой, и запишите
его вместе с запросами. В конце вы удалите только эту таблицу.
## Выберите сырой срез
STG хранит строки примерно три дня по `_load_ts`. Эта колонка отмечает время
поступления в хранилище. Модельный день события лежит в `EventDate`. Сначала
найдите среди недавних данных завершившийся интервал со строками. Посмотрите
счет по минутам:
```sql
SELECT
toStartOfMinute(_load_ts) AS minute,
count() AS rows
FROM stg.hits_raw_dist
WHERE _load_ts >= now() - INTERVAL 30 MINUTE
AND _load_ts < toStartOfMinute(now())
GROUP BY minute
ORDER BY minute DESC;
```
Выберите короткий интервал `[slice_from, slice_to)`, который уже закончился и
дает удобное для построчной проверки число строк. Если одна минута велика,
сузьте ее точными границами `_load_ts`. Если недавних строк нет, откройте
Airflow. При включенном `world_live` посмотрите его текущий запуск и дочерний
`world_live_day`: дождитесь свежих строк от текущего или следующего планового
запуска, а при ошибке сначала разберите ее причину. Запустите `world_live_day`
вручную, только если `world_live` стоит на паузе и активного `world_live_day`
нет. Успешный прогон распределяет события модельного дня по реальным минутам.
Зафиксируйте до разбора:
- обе границы с миллисекундами и число строк;
- минимальный и максимальный `kafka_offset` по каждой паре
`kafka_topic`, `kafka_partition`.
Во всех дальнейших запросах читайте `stg.hits_raw_dist` только с этими
границами. Новые строки с более поздним `_load_ts` в срез не попадут. Повторите
опыт до очистки по TTL; после нее прежний срез уже не восстановить из ODS.
## Соберите собственную таблицу
Таблица содержит все 47 полей источника с именами и типами
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
`Array(Decimal(18, 2))`.
Обработайте повторные доставки так же, как ODS. Возьмите
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
локальной таблицы: загрузка и сверка в этом опыте выполняются на одной ноде.
`WatchID` — логический идентификатор события, уникальный по
[договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS
шире: в `ReplacingMergeTree` версиями считаются строки с одинаковым полным
ключом сортировки. Используйте его при загрузке и сверке.
## Разберите годные строки
Для каждой строки среза проверьте те же условия приема, что и на стенде:
1. корень `raw` — JSON-объект;
2. набор верхнеуровневых ключей в точности равен 47 ключам формата;
3. `WatchID`, `VisitID`, `ClientID`, `EventDate` и `UTCEventTime` разбираются
в ожидаемые типы.
Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами
выберите способ безопасного извлечения пяти опорных полей, форму общих выражений
и место фильтра. Остальные поля извлеките по описанию формата.
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
таблицу только годные строки выбранного среза.
Считайте отвергнутые строки прямо в исходном срезе, без отдельной таблицы
брака. Одним запросом посчитайте:
- все доставки;
- строки, прошедшие ваше условие отбора;
- строки, для которых это условие ложно.
Проверьте равенство общего числа строк сумме прошедших и отвергнутых. Так каждая
исходная строка учитывается ровно в одной группе. Если отвергнутые строки есть,
сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же
координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если
брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение
условия отбора на этом срезе, но не классификатор ошибок. Подкладывать грязные
сообщения ради задания не требуется.
## Приведите стороны к одному зерну
### Учтите повторные доставки
В STG одна строка представляет доставку. Повтор сообщения остается второй
строкой. В `ods.event_v` одна строка представляет актуальное событие, потому
что представление читает физическую таблицу с `FINAL`.
Свою сторону тоже читайте с `FINAL`. Ожидаемый счет равен числу различных
полных ключей среди годных доставок. Разница между числом доставок и этим
счетом показывает повторы в исходном срезе.
### Выберите события ODS для сравнения
Теперь нужно найти в ODS события из выбранного среза. Список стройте по
исходным доставкам: если взять его из своей таблицы, ошибочно отвергнутое
событие исчезнет с обеих сторон сверки и потеря останется незамеченной.
Снова возьмите весь исходный STG-срез. По координатам Kafka исключите
доставки, которые есть в `ods.event_errors_dist`. У оставшихся извлеките
`WatchID` без риска прервать запрос и выберите эти события из свежего
`ods.event_v`. Свое условие отбора здесь не применяйте: решение о годности
доставки вы берете из результата стендового разбора.
`WatchID` связывает доставку с событием в ODS. Для удаления повторов и
сравнения полей дальше используйте полный физический ключ из схемы таблицы.
## Сверьте 47 полей
Соедините свою таблицу и выбранные строки `ods.event_v` через `FULL OUTER
JOIN` по полному ключу замены. Полное соединение сохраняет строки,
которых нет на одной из сторон. Разделите результат на три исхода:
- строка есть только в ODS — вы ее потеряли;
- строка есть только у вас — вы приняли лишнее или иначе разобрали часть
ключа;
- ключ есть с обеих сторон, но различается хотя бы одно из 47 полей.
ClickHouse по умолчанию заполняет отсутствующую сторону значениями типов.
Включите для сверки `join_use_nulls = 1` или добавьте явный признак присутствия
в каждую сторону. Не определяйте отсутствие по значениям полей события.
Сравните все поля явно, включая массивы, `ecommerce` и точное десятичное
`purchaseRevenue`. Удобный вид диагностики выберите сами: важно, чтобы по
результату можно было назвать отличающиеся колонки одной строки. Выведите число
строк каждого из трех исходов. В верном разборе все три числа равны нулю.
`_load_ts` не входит в 47 полей источника и не проверяет JSON-разбор.
Проверьте его отдельно. Свяжите каждую строку своей таблицы с меткой одной из
доставок того же полного ключа в исходном срезе. После `FINAL` остается самая
поздняя метка этого ключа внутри среза. С ODS ее не сравнивайте: там может
храниться более поздняя доставка того же события из-за границ среза.
## Расследуйте расхождение
Если сверка не пуста, возьмите одну строку. Вернитесь к независимому списку
ключей и исходному `raw`, выведите рядом координаты Kafka и `_load_ts`.
Для измененной строки назовите поля, которые расходятся, и сравните каждое с
сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и
результат условия отбора. Если координаты доставки есть в
`ods.event_errors_dist`, посмотрите `error_class`.
Исправьте разбор, удалите и заново создайте только собственную таблицу, затем
повторите вставку того же среза. Остановитесь, когда счет исходных доставок по
прежним границам не изменился, число годных и отвергнутых строк в сумме дает
число исходных доставок, а построчная сверка пуста.
## Чего не подтверждает сравнение с ODS
`ods.event_v` — образец для сравнения. Оба разбора могли повторить одну
ошибку. Для пяти опорных полей у вас есть проверка преобразования в нужный тип
и исходный JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако
второго независимого источника для всех значений на стенде нет.
## Уберите свой объект
Сохраните DDL таблицы, запрос разбора, границы среза, число исходных, годных
и отвергнутых строк и итоговую сверку без расхождений. Запишите также, чего
не подтверждает сравнение с ODS и как TTL ограничивает время для повторения
опыта. Затем удалите свою таблицу командой `DROP TABLE` с ее точным именем.
Убедитесь через `system.tables`, что объекта больше нет.
Прикладные строки стенда вы не меняли, поэтому удалять данные из STG, ODS или
Kafka после опыта не нужно.
## Что должно получиться
- [ ] у вас сохранены границы выбранного среза, число строк и координаты
доставки по партициям Kafka;
- [ ] ваша таблица содержит все 47 полей и `_load_ts` и обрабатывает
повторы по тем же правилам, что и ODS;
- [ ] ваш разбор принимает только JSON-объекты с точным набором ключей и
пятью опорными полями, которые преобразуются в нужные типы;
- [ ] вы сверили число исходных строк с суммой годных и отвергнутых,
а найденный брак разобрали по `raw`, координатам и классу ошибки стенда;
- [ ] вы выбрали события ODS по исходному срезу и таблице ошибок,
независимо от своей таблицы и условия отбора;
- [ ] вы сравнили обе стороны по полному физическому ключу через
`FULL OUTER JOIN`: во всех 47 полях нет расхождений,
пропавших и лишних строк тоже нет;
- [ ] вы отдельно проверили перенос `_load_ts` и учли повторные доставки;
- [ ] вы записали ограничения сравнения с ODS и срок повторения опыта;
- [ ] после опыта вы удалили свою таблицу, сохранив объекты и данные стенда.