diff --git a/docs/labs/6-1-event-parsing.md b/docs/labs/6-1-event-parsing.md index cd0ab8b..b817729 100644 --- a/docs/labs/6-1-event-parsing.md +++ b/docs/labs/6-1-event-parsing.md @@ -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`, однако второго независимого источника для всех значений на стенде нет.