docs(labs): добавлено задание по разбору событий #241
@@ -49,9 +49,12 @@ ORDER BY minute DESC;
|
|||||||
|
|
||||||
Выберите короткий интервал `[slice_from, slice_to)`, который уже закончился и
|
Выберите короткий интервал `[slice_from, slice_to)`, который уже закончился и
|
||||||
дает удобное для построчной проверки число строк. Если одна минута велика,
|
дает удобное для построчной проверки число строк. Если одна минута велика,
|
||||||
сузьте ее точными границами `_load_ts`. Если недавних строк нет, запустите
|
сузьте ее точными границами `_load_ts`. Если недавних строк нет, откройте
|
||||||
`world_live_day`: он распределит события модельного дня по реальным минутам.
|
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`.
|
`kafka_topic`, `kafka_partition`.
|
||||||
|
|
||||||
Во всех дальнейших запросах читайте `stg.hits_raw_dist` только с этими
|
Во всех дальнейших запросах читайте `stg.hits_raw_dist` только с этими
|
||||||
границами. Зафиксированная верхняя граница защищает срез от нового потока.
|
границами. Новые строки с более поздним `_load_ts` в срез не попадут. Повторите
|
||||||
Повторить опыт можно, пока строки не удалил TTL; после этого прежний срез уже
|
опыт до очистки по TTL; после нее прежний срез уже не восстановить из ODS.
|
||||||
не восстановить из ODS.
|
|
||||||
|
|
||||||
## Соберите собственную таблицу
|
## Соберите собственную таблицу
|
||||||
|
|
||||||
@@ -70,47 +72,45 @@ ORDER BY minute DESC;
|
|||||||
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
|
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
|
||||||
`Array(Decimal(18, 2))`.
|
`Array(Decimal(18, 2))`.
|
||||||
|
|
||||||
Повтор доставки должен схлопываться так же, как в ODS. Возьмите
|
Схлопните повтор доставки по договору ODS. Возьмите
|
||||||
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
|
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
|
||||||
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
|
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
|
||||||
локальной таблицы: отдельная Distributed-таблица не добавит урока к пакетному
|
локальной таблицы: отдельная Distributed-таблица не добавит урока к пакетному
|
||||||
опыту на одной ноде.
|
опыту на одной ноде.
|
||||||
|
|
||||||
`WatchID` - логический идентификатор события, уникальный по
|
`WatchID` — логический идентификатор события, уникальный по
|
||||||
[договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS
|
[договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS
|
||||||
шире: `ReplacingMergeTree` считает версиями строки с одинаковым полным ключом
|
шире: в `ReplacingMergeTree` версиями считаются строки с одинаковым полным
|
||||||
сортировки. Используйте его при загрузке и сверке.
|
ключом сортировки. Используйте его при загрузке и сверке.
|
||||||
|
|
||||||
## Разберите годные строки
|
## Разберите годные строки
|
||||||
|
|
||||||
Для каждой строки среза примите решение по тому же строгому входу, что и
|
Для каждой строки среза примите решение по тому же строгому входу, что и
|
||||||
стенд:
|
стенд:
|
||||||
|
|
||||||
1. корень `raw` - JSON-объект;
|
1. корень `raw` — JSON-объект;
|
||||||
2. набор верхнеуровневых ключей в точности равен 47 ключам формата;
|
2. набор верхнеуровневых ключей в точности равен 47 ключам формата;
|
||||||
3. `WatchID`, `VisitID`, `ClientID`, `EventDate` и `UTCEventTime` разбираются
|
3. `WatchID`, `VisitID`, `ClientID`, `EventDate` и `UTCEventTime` разбираются
|
||||||
в ожидаемые типы.
|
в ожидаемые типы.
|
||||||
|
|
||||||
Пять опорных полей сначала разбирайте в `Nullable`. К преобразованию в
|
Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами
|
||||||
необнуляемый тип переходите после фильтра, чтобы грязная строка не остановила
|
выберите способ безопасного извлечения пяти опорных полей, форму общих выражений
|
||||||
весь пакет исключением. Остальные поля извлеките по договору формата. Выберите
|
и место фильтра. Остальные поля извлеките по договору формата.
|
||||||
сами форму общих выражений и способ не повторять их внутри запроса. Итог
|
|
||||||
предиката всегда должен быть равен 0 или 1: `NULL` не должен потеряться между
|
|
||||||
предикатом и его отрицанием.
|
|
||||||
|
|
||||||
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
|
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
|
||||||
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
|
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
|
||||||
таблицу только годные строки выбранного среза.
|
таблицу только годные строки выбранного среза.
|
||||||
|
|
||||||
Отдельную таблицу брака строить не нужно. Но решение строгого входа должно
|
Считайте отвергнутые строки прямо в исходном срезе: отдельная таблица брака в
|
||||||
быть наблюдаемым. Одним запросом посчитайте для исходного среза:
|
задание не входит. Проверьте работу строгого входа одним запросом. Посчитайте:
|
||||||
|
|
||||||
- все доставки;
|
- все доставки;
|
||||||
- строки, прошедшие ваш предикат;
|
- строки, прошедшие ваш предикат;
|
||||||
- строки, отвергнутые его отрицанием.
|
- строки, отвергнутые его отрицанием.
|
||||||
|
|
||||||
Первая величина должна равняться сумме двух следующих. Если отвергнутые строки
|
Проверьте равенство общего числа строк сумме прошедших и отвергнутых. Так каждая
|
||||||
есть, сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же
|
исходная строка учитывается ровно в одной группе. Если отвергнутые строки есть,
|
||||||
|
сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же
|
||||||
координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если
|
координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если
|
||||||
брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение
|
брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение
|
||||||
предиката на этом срезе, но не классификатор ошибок. Подкладывать грязные
|
предиката на этом срезе, но не классификатор ошибок. Подкладывать грязные
|
||||||
@@ -127,24 +127,25 @@ ORDER BY minute DESC;
|
|||||||
счетом показывает повторы в исходном срезе.
|
счетом показывает повторы в исходном срезе.
|
||||||
|
|
||||||
Область эталона постройте независимо от своего предиката и результата вставки.
|
Область эталона постройте независимо от своего предиката и результата вставки.
|
||||||
Снова возьмите весь исходный STG-срез. По координатам Kafka отделите доставки,
|
Снова возьмите весь исходный STG-срез. По координатам Kafka исключите доставки,
|
||||||
которые стенд записал в `ods.event_errors_dist`. У оставшихся доставок извлеките
|
которые есть в `ods.event_errors_dist`. У оставшихся доставок извлеките
|
||||||
`WatchID` в `Nullable` и выберите эти события из свежего `ods.event_v`.
|
`WatchID` без риска прервать запрос и выберите эти события из свежего
|
||||||
|
`ods.event_v`.
|
||||||
|
|
||||||
Здесь `WatchID` служит мостом между сырой доставкой и ODS: по договору
|
Свяжите сырую доставку с ODS по `WatchID`: по договору генератора это уникальный
|
||||||
генератора это уникальный идентификатор события. Он задает только область
|
идентификатор события. Он задает только область эталона. Дедупликация и
|
||||||
эталона. Дедупликация и последующее соединение по-прежнему используют полный
|
последующее соединение используют полный физический ключ. Не берите область из
|
||||||
физический ключ. Не берите область из своей таблицы и не применяйте к ней свой
|
своей таблицы и не применяйте к ней свой предикат. Иначе ошибочно отвергнутая
|
||||||
предикат. Иначе ошибочно отвергнутая строка исчезнет с обеих сторон сверки.
|
строка исчезнет с обеих сторон сверки.
|
||||||
|
|
||||||
## Сверьте 47 полей
|
## Сверьте 47 полей
|
||||||
|
|
||||||
Соедините свою таблицу и выбранные строки `ods.event_v` через `FULL OUTER
|
Соедините свою таблицу и выбранные строки `ods.event_v` через `FULL OUTER
|
||||||
JOIN` по полному ключу замены. Полное соединение должно сохранить строки,
|
JOIN` по полному ключу замены. Полное соединение сохраняет строки,
|
||||||
которых нет на одной из сторон. Разделите результат на три исхода:
|
которых нет на одной из сторон. Разделите результат на три исхода:
|
||||||
|
|
||||||
- строка есть только в ODS - вы ее потеряли;
|
- строка есть только в ODS — вы ее потеряли;
|
||||||
- строка есть только у вас - вы приняли лишнее или иначе разобрали часть
|
- строка есть только у вас — вы приняли лишнее или иначе разобрали часть
|
||||||
ключа;
|
ключа;
|
||||||
- ключ есть с обеих сторон, но различается хотя бы одно из 47 полей.
|
- ключ есть с обеих сторон, но различается хотя бы одно из 47 полей.
|
||||||
|
|
||||||
@@ -154,16 +155,14 @@ ClickHouse по умолчанию заполняет отсутствующую
|
|||||||
|
|
||||||
Сравните все поля явно, включая массивы, `ecommerce` и точное десятичное
|
Сравните все поля явно, включая массивы, `ecommerce` и точное десятичное
|
||||||
`purchaseRevenue`. Удобный вид диагностики выберите сами: важно, чтобы по
|
`purchaseRevenue`. Удобный вид диагностики выберите сами: важно, чтобы по
|
||||||
результату можно было назвать отличающиеся колонки одной строки. Итоговая
|
результату можно было назвать отличающиеся колонки одной строки. Выведите число
|
||||||
сводка должна показать число строк каждого из трех исходов. В верном разборе
|
строк каждого из трех исходов. В верном разборе все три числа равны нулю.
|
||||||
все три числа равны нулю.
|
|
||||||
|
|
||||||
`_load_ts` не входит в 47 полей источника и не проверяет JSON-разбор.
|
`_load_ts` не входит в 47 полей источника и не проверяет JSON-разбор.
|
||||||
Проверьте его отдельно: у каждой строки вашей таблицы метка должна быть меткой
|
Проверьте его отдельно. Свяжите каждую строку своей таблицы с меткой одной из
|
||||||
одной из доставок того же полного ключа в исходном срезе, а после `FINAL`
|
доставок того же полного ключа в исходном срезе. После `FINAL` остается самая
|
||||||
должна остаться самая поздняя метка этого ключа внутри среза. С ODS ее не
|
поздняя метка этого ключа внутри среза. С 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`, однако второго
|
JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако второго
|
||||||
независимого источника для всех значений на стенде нет.
|
независимого источника для всех значений на стенде нет.
|
||||||
|
|||||||
Reference in new issue
Block a user