# Разбор заказов в ODS Вы перепишете разбор заказов в ODS. На четырех пробных сообщениях проверите, что правильно заполненный заказ и сообщения с ошибками попадают в соответствующие таблицы без потерь. ## Подготовка - [проверки заказов](5-1-order-checks.md); - [прием заказов в ODS](../architecture/orders/ingestion.md); - [раскладку по шардам](../architecture/storage.md#раскладка-по-шардам); - [`orders_ingest`](../../dags/orders_ingest.py); - [`order_parse.sql`](../../sql/ods/order_parse.sql). ## Порядок работы Сначала проведите опыт на готовом `order_parse.sql`. Затем удалите его SQL, соберите разбор заново и повторите опыт с другим маркером. Сохраните устройство `orders_ingest`, DDL, Kafka, таблиц и преобразований DDS. После каждого опыта удалите только добавленные вами строки. Перед началом убедитесь, что после [предыдущего задания](5-1-order-checks.md) все семь проверок качества прошли успешно. Поставьте `world_live` на паузу, дождитесь активных прогонов и до завершения опыта не запускайте `world_next_day`, `world_live_day`, `world_initialize` и `world_recreate`. Не запускайте `etl_pipeline` между отправкой пробной пачки и ее очисткой. ## Пробные сообщения Сделайте четыре сообщения с общим уникальным маркером: 1. годную строку с маркером в `order_id`; 2. JSON-строку вместо объекта - класс `not_an_object`; 3. объект с лишним ключом - класс `keyset_mismatch`; 4. объект с точным набором ключей, но строкой в `user_id` - класс `field_invalid`. `not_an_object` означает, что корень JSON не объект. `keyset_mismatch` означает, что у объекта есть лишние верхнеуровневые ключи или не хватает обязательных. `field_invalid` означает, что ключи на месте, но значение имеет неверный тип, формат или не помещается в заданный тип данных. Соберите строки в `/tmp/<маркер>.jsonl`. Заготовка дает одну годную строку и по одному примеру каждого класса брака: ```bash LAB_MARKER="lab5-$(date +%s)" LAB_BATCH="/tmp/${LAB_MARKER}.jsonl" LAB_DAY="$( docker compose exec -T clickhouse-01 sh -lc \ 'clickhouse-client --user etl --password "$CLICKHOUSE_ETL_PASSWORD" \ --query "SELECT toString(max(snapshot_date)) FROM ods.order_dist"' )" jq -cn --arg marker "$LAB_MARKER" --arg day "$LAB_DAY" ' def good($id): { order_id: $id, user_id: 1, status: "paid", created_at: ($day + "T00:00:00.000Z"), updated_at: ($day + "T01:00:00.000Z"), items_total: "10.00", discount: "0.00", delivery: "0.00", total: "10.00", items: [{sku: "lab5", qty: 1, price: "10.00"}], snapshot_date: $day }; good($marker), ($marker + "-not-an-object"), (good($marker + "-keys") + {lab_marker: true}), (good($marker + "-field") | .user_id = "broken") ' > "$LAB_BATCH" ``` Отправьте файл в обычный топик заказов утилитой из контейнера Kafka: ```bash docker compose exec -T kafka /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:9092 --topic orders < "$LAB_BATCH" ``` Запустите `orders_ingest`. Найдите свои строки по маркеру в `stg.orders_raw_dist` и сохраните их `_load_id`: это идентификатор запуска, который принял сообщения. Он должен быть одинаковым у всех четырех строк. ## Проверка готового разбора До запуска `etl_pipeline` проверьте строки с найденным `_load_id`: - в STG четыре строки; - в таблице `ods.order_dist` одна годная строка; - в `ods.order_errors_dist` три строки, по одной каждого класса; - `STG = годные + брак`; - поля заказа в текущей строке `ods.order_v` совпадают с полями из годного исходного сообщения; последнюю версию для сравнения выберите через `argMax` по `snapshot_date`. Считайте строки `ods.order_dist` именно по `_load_id`: представление `ods.order_v` оставляет только текущую версию каждого заказа и не показывает, сколько строк разобрал запуск. Запрос сверки с `argMax` напишите сами. Значения для сравнения извлеките из `raw` пробных сообщений. Отдельно сравните `created_at` и `updated_at`: в заготовке они намеренно различаются. Проверьте раскладку годной строки через `hostName()` на `ods.order_dist`. Узел, на котором лежит строка, должен соответствовать остатку `cityHash64(order_id) % 2`: 0 - `clickhouse-01`, 1 - `clickhouse-02`. ## Удаление пробных сообщений Удалите только строки маркера из локальных таблиц: ```sql ALTER TABLE stg.orders_raw_rep ON CLUSTER clickstream_cluster DELETE WHERE position(raw, '<маркер>') > 0 SETTINGS mutations_sync = 2; ALTER TABLE ods.order_rep ON CLUSTER clickstream_cluster DELETE WHERE startsWith(order_id, '<маркер>') SETTINGS mutations_sync = 2; ALTER TABLE ods.order_errors_rep ON CLUSTER clickstream_cluster DELETE WHERE position(raw, '<маркер>') > 0 SETTINGS mutations_sync = 2; ``` В ClickHouse 26.3 мутации выполняются в фоне. С `mutations_sync = 2` команда ждет их завершения на всех репликах. Запросами к трем Distributed-таблицам убедитесь, что строк маркера не осталось, затем удалите временный файл. Группа `clickstream_orders` уже подтвердила эти сообщения, поэтому обычный повтор `orders_ingest` их не прочитает. В Kafka они остаются до удаления по политике хранения. `world_recreate` удаляет группу и сам топик, поэтому в пересозданный мир пробная пачка не переходит. Очистка должна закончиться до запуска `etl_pipeline`. Если конвейер все же успел собрать временный заказ в DDS, сначала очистите три таблицы выше, затем запустите `etl_pipeline`: пересборка текущего окна удалит заказ из DDS и обновит витрины. ## Собственный разбор заказов Удалите готовый SQL из `sql/ods/order_parse.sql` и напишите свой. Чтобы `orders_ingest` мог его использовать, сохраните следующие условия: - запрос читает строки `stg.orders_raw_dist` с переданным `{load_id:String}`; - каждая прочитанная строка попадает ровно в один результат: годный заказ или брак; - ошибка в `raw` одной строки не прерывает разбор остальных; - годные строки записываются в `ods.order_dist`, остальные - в `ods.order_errors_dist`; - классы брака и их порядок - `not_an_object`, `keyset_mismatch`, `field_invalid`; - метки доставки и `_load_id` переходят из STG без пересчета; - задача завершается после записи и заказов, и ошибок на шарды через Distributed; строки распределяются по ключу соответствующей целевой таблицы. Точный набор полей, типы и условия проверки значений описаны в [документе приема](../architecture/orders/ingestion.md#граница-строгого-приёма), а целевые колонки - в [`20-ods-tables.sql`](../../sql/ddl/20-ods-tables.sql). Содержимое `items` сохраните как JSON: его разбирает DDS. Повторите опыт с новым маркером. Получите тот же баланс 4 = 1 + 3, те же три класса, верную текущую версию заказа по `snapshot_date`, разные значения `created_at` и `updated_at` и правильный шард. Затем удалите строки второго опыта теми же командами и запустите `dq_check`. ## Завершение работы Убедитесь, что оба маркера отсутствуют в STG, ODS, таблице брака и DDS, временные файлы удалены, а семь проверок успешны. Теперь можно перейти к сборке DDS, не снимая паузу с модельного времени. Если вы прерываетесь и до опыта `world_live` работал, снимите его с паузы. ## Результат работы - [ ] у вас сохранены результаты до переписывания: четыре строки дали один заказ и три класса брака; вы сверили поля текущей версии и шард; - [ ] вы переписали `sql/ods/order_parse.sql`, сохранив остальные части стенда; - [ ] ваш разбор дал тот же результат на новой пробной пачке; - [ ] вы проверили разные значения `created_at` и `updated_at` у годной строки в обоих опытах и подтвердили правильный шард через `hostName()`; - [ ] строки обоих опытов удалены по их маркерам; - [ ] после очистки конвейер и все семь проверок успешны.