docs(labs): уточнено ТЗ разбора событий по ревью

- Зачем:
  - сделать постановку ступени 6 понятнее после редакторского ревью, сохранив её учебный договор и границы.
- Что:
  - оставлена свобода выбрать форму разбора без навязывания Nullable и готовой схемы 0/1.
  - уточнены пакетная обработка, баланс годных строк, модельное и ручное время и убрана лишняя персонификация.
- Проверка:
  - проверены SHA256, `git diff --check` и изменение только `docs/labs/6-1-event-parsing.md`.
This commit is contained in:
ddadmin committed 2026-09-07 03:19:58 -04:00
1 parent 7918eeed25
commit 6e4680d12d
1 file changed
+42 -43
+42 -43
View File
@@ -49,9 +49,12 @@ ORDER BY minute DESC;
Выберите короткий интервал `[slice_from, slice_to)`, который уже закончился и
дает удобное для построчной проверки число строк. Если одна минута велика,
сузьте ее точными границами `_load_ts`. Если недавних строк нет, запустите
`world_live_day`: он распределит события модельного дня по реальным минутам.
Затем повторите поиск. Службы для задания останавливать не нужно.
сузьте ее точными границами `_load_ts`. Если недавних строк нет, откройте
Airflow. При включенном `world_live` посмотрите его текущий запуск и дочерний
`world_live_day`: дождитесь свежих строк от текущего или следующего планового
запуска, а при ошибке сначала разберите ее причину. Запустите `world_live_day`
вручную, только если `world_live` стоит на паузе и активного `world_live_day`
нет. Успешный прогон распределяет события модельного дня по реальным минутам.
Зафиксируйте до разбора:
@@ -60,9 +63,8 @@ ORDER BY minute DESC;
`kafka_topic`, `kafka_partition`.
Во всех дальнейших запросах читайте `stg.hits_raw_dist` только с этими
границами. Зафиксированная верхняя граница защищает срез от нового потока.
Повторить опыт можно, пока строки не удалил TTL; после этого прежний срез уже
не восстановить из ODS.
границами. Новые строки с более поздним `_load_ts` в срез не попадут. Повторите
опыт до очистки по TTL; после нее прежний срез уже не восстановить из ODS.
## Соберите собственную таблицу
@@ -70,47 +72,45 @@ ORDER BY minute DESC;
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
`Array(Decimal(18, 2))`.
Повтор доставки должен схлопываться так же, как в ODS. Возьмите
Схлопните повтор доставки по договору ODS. Возьмите
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
локальной таблицы: отдельная Distributed-таблица не добавит урока к пакетному
опыту на одной ноде.
`WatchID` - логический идентификатор события, уникальный по
`WatchID` — логический идентификатор события, уникальный по
[договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS
шире: `ReplacingMergeTree` считает версиями строки с одинаковым полным ключом
сортировки. Используйте его при загрузке и сверке.
шире: в `ReplacingMergeTree` версиями считаются строки с одинаковым полным
ключом сортировки. Используйте его при загрузке и сверке.
## Разберите годные строки
Для каждой строки среза примите решение по тому же строгому входу, что и
стенд:
1. корень `raw` - JSON-объект;
1. корень `raw` — JSON-объект;
2. набор верхнеуровневых ключей в точности равен 47 ключам формата;
3. `WatchID`, `VisitID`, `ClientID`, `EventDate` и `UTCEventTime` разбираются
в ожидаемые типы.
Пять опорных полей сначала разбирайте в `Nullable`. К преобразованию в
необнуляемый тип переходите после фильтра, чтобы грязная строка не остановила
весь пакет исключением. Остальные поля извлеките по договору формата. Выберите
сами форму общих выражений и способ не повторять их внутри запроса. Итог
предиката всегда должен быть равен 0 или 1: `NULL` не должен потеряться между
предикатом и его отрицанием.
Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами
выберите способ безопасного извлечения пяти опорных полей, форму общих выражений
и место фильтра. Остальные поля извлеките по договору формата.
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
таблицу только годные строки выбранного среза.
Отдельную таблицу брака строить не нужно. Но решение строгого входа должно
быть наблюдаемым. Одним запросом посчитайте для исходного среза:
Считайте отвергнутые строки прямо в исходном срезе: отдельная таблица брака в
задание не входит. Проверьте работу строгого входа одним запросом. Посчитайте:
- все доставки;
- строки, прошедшие ваш предикат;
- строки, отвергнутые его отрицанием.
Первая величина должна равняться сумме двух следующих. Если отвергнутые строки
есть, сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же
Проверьте равенство общего числа строк сумме прошедших и отвергнутых. Так каждая
исходная строка учитывается ровно в одной группе. Если отвергнутые строки есть,
сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же
координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если
брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение
предиката на этом срезе, но не классификатор ошибок. Подкладывать грязные
@@ -127,24 +127,25 @@ ORDER BY minute DESC;
счетом показывает повторы в исходном срезе.
Область эталона постройте независимо от своего предиката и результата вставки.
Снова возьмите весь исходный STG-срез. По координатам Kafka отделите доставки,
которые стенд записал в `ods.event_errors_dist`. У оставшихся доставок извлеките
`WatchID` в `Nullable` и выберите эти события из свежего `ods.event_v`.
Снова возьмите весь исходный STG-срез. По координатам Kafka исключите доставки,
которые есть в `ods.event_errors_dist`. У оставшихся доставок извлеките
`WatchID` без риска прервать запрос и выберите эти события из свежего
`ods.event_v`.
Здесь `WatchID` служит мостом между сырой доставкой и ODS: по договору
генератора это уникальный идентификатор события. Он задает только область
эталона. Дедупликация и последующее соединение по-прежнему используют полный
физический ключ. Не берите область из своей таблицы и не применяйте к ней свой
предикат. Иначе ошибочно отвергнутая строка исчезнет с обеих сторон сверки.
Свяжите сырую доставку с ODS по `WatchID`: по договору генератора это уникальный
идентификатор события. Он задает только область эталона. Дедупликация и
последующее соединение используют полный физический ключ. Не берите область из
своей таблицы и не применяйте к ней свой предикат. Иначе ошибочно отвергнутая
строка исчезнет с обеих сторон сверки.
## Сверьте 47 полей
Соедините свою таблицу и выбранные строки `ods.event_v` через `FULL OUTER
JOIN` по полному ключу замены. Полное соединение должно сохранить строки,
JOIN` по полному ключу замены. Полное соединение сохраняет строки,
которых нет на одной из сторон. Разделите результат на три исхода:
- строка есть только в ODS - вы ее потеряли;
- строка есть только у вас - вы приняли лишнее или иначе разобрали часть
- строка есть только в ODS — вы ее потеряли;
- строка есть только у вас — вы приняли лишнее или иначе разобрали часть
ключа;
- ключ есть с обеих сторон, но различается хотя бы одно из 47 полей.
@@ -154,16 +155,14 @@ ClickHouse по умолчанию заполняет отсутствующую
Сравните все поля явно, включая массивы, `ecommerce` и точное десятичное
`purchaseRevenue`. Удобный вид диагностики выберите сами: важно, чтобы по
результату можно было назвать отличающиеся колонки одной строки. Итоговая
сводка должна показать число строк каждого из трех исходов. В верном разборе
все три числа равны нулю.
результату можно было назвать отличающиеся колонки одной строки. Выведите число
строк каждого из трех исходов. В верном разборе все три числа равны нулю.
`_load_ts` не входит в 47 полей источника и не проверяет JSON-разбор.
Проверьте его отдельно: у каждой строки вашей таблицы метка должна быть меткой
одной из доставок того же полного ключа в исходном срезе, а после `FINAL`
должна остаться самая поздняя метка этого ключа внутри среза. С ODS ее не
сравнивайте: там могла победить более поздняя доставка того же события из-за
границ среза.
Проверьте его отдельно. Свяжите каждую строку своей таблицы с меткой одной из
доставок того же полного ключа в исходном срезе. После `FINAL` остается самая
поздняя метка этого ключа внутри среза. С ODS ее не сравнивайте: там может
храниться более поздняя доставка того же события из-за границ среза.
## Расследуйте расхождение
@@ -172,8 +171,8 @@ ClickHouse по умолчанию заполняет отсутствующую
Для измененной строки назовите поля, которые расходятся, и сравните каждое с
сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и
решение строгого предиката. Если строку отверг стенд, найдите ее в
`ods.event_errors_dist` по координатам доставки и посмотрите `error_class`.
решение строгого предиката. Если координаты доставки есть в
`ods.event_errors_dist`, посмотрите `error_class`.
Исправьте разбор, удалите и заново создайте только собственную таблицу, затем
повторите вставку того же среза. Остановитесь, когда счет исходных доставок по
@@ -182,7 +181,7 @@ ClickHouse по умолчанию заполняет отсутствующую
## Где заканчивается проверка
Стендовый ODS дает образец для сравнения. Оба разбора могли повторить одну
`ods.event_v` — образец для сравнения. Оба разбора могли повторить одну
ошибку. Для пяти опорных полей у вас есть проверка разбираемости и исходный
JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако второго
независимого источника для всех значений на стенде нет.