- Зачем: - одно длинное ТЗ заставляло одновременно держать в голове три самостоятельных опыта и их очистку. - Что: - проверки, разбор ODS и сборка DDS разнесены по трем последовательным заданиям. - уточнены границы учебной реализации, место диагностики и выбор закрытого дня. - оглавление ступени 5 дополнено ссылками на все три задания. - Проверка: - проверены ссылки, пробная пачка jq, кластерные мутации и отсутствие ошибок пробелов в diff.
149 lines
9.5 KiB
Markdown
149 lines
9.5 KiB
Markdown
# Сначала сторож, потом ломай: заказы
|
||
|
||
**Чему учит:** до рискованного изменения задавать проверке явный договор и
|
||
испытывать его управляемой ошибкой.
|
||
|
||
## Сначала прочитать
|
||
|
||
- [модель заказа DDS](../architecture/dds/order.md);
|
||
- [дневной конвейер](../architecture/etl.md#объём-прогона);
|
||
- [правила проверок](../architecture/testing.md#проверка-которая-не-умеет-краснеть-бесполезна).
|
||
|
||
Посмотрите также на устройство одной проверки в [`sql/dq/`](../../sql/dq/)
|
||
и на ее включение в [`dq_check`](../../dags/dq_check.py). Готового ответа для
|
||
двух проверок из этого задания в репозитории нет.
|
||
|
||
## Граница задания
|
||
|
||
Добавьте два сторожа:
|
||
|
||
- `sql/dq/order_items_total.sql` проверяет сумму позиций заказа;
|
||
- `sql/dq/orders_vs_ods.sql` сверяет DDS с его источником;
|
||
- `dags/dq_check.py` запускает обе проверки, публикует их строки в
|
||
`dm.dq_summary` и создает для них отдельные задачи.
|
||
|
||
На этом шаге не меняйте разбор ODS и сборку DDS. Даги, DDL, таблицы-доноры,
|
||
Kafka и службы остаются несущей частью упражнения.
|
||
|
||
Перед опытами остановите автоматическое движение мира: поставьте `world_live`
|
||
на паузу и дождитесь завершения активных прогонов. До чистой точки остановки не
|
||
запускайте руками `world_next_day`, `world_live_day`, `world_initialize` и
|
||
`world_recreate`. Это условие стабильного модельного времени: одной паузы
|
||
`world_live` для него мало.
|
||
|
||
## Два договора качества
|
||
|
||
Обе проверки следуют договору `dq_check`: запрос возвращает по одной строке
|
||
на день с колонками `data_date`, `reference_rows`, `actual_rows`,
|
||
`failed_rows`, `diagnostics`. Даг сохраняет в `dm.dq_summary` только счетчики,
|
||
статус и координаты запуска. Ограниченные примеры из `diagnostics` ищите в
|
||
журнале упавшей задачи Airflow. Ограничьте их тем же малым числом, что и
|
||
существующие запросы в `sql/dq/`.
|
||
|
||
### Сумма позиций
|
||
|
||
Имя проверки - `order_items_total`. Для каждого заказа из `dds.order_v`
|
||
посчитайте сумму `item_price * item_quantity` и сравните ее с `items_total`.
|
||
`reference_rows` считает ключи со стороны `items_total`, `actual_rows` - ключи
|
||
со стороны вычисленной суммы, а `failed_rows` - несовпавшие значения. В
|
||
исправном наборе первые два счетчика равны по построению; сам инвариант выражен
|
||
через `failed_rows`. Для диагностики достаточно `order_id`, ожидаемой суммы и
|
||
суммы позиций.
|
||
|
||
Проверка относится к типизированному разбору массива `items` на границе
|
||
ODS -> DDS. Скидка, доставка и равенство `total` в этот договор не входят.
|
||
|
||
### DDS против ODS
|
||
|
||
Имя проверки - `orders_vs_ods`. Источник модели DDS здесь - текущее состояние
|
||
`ods.order_v`. Внутреннее состояние генератора в этот договор не входит.
|
||
Одним запросом соберите обе стороны на зерне дня заказа и сравните:
|
||
|
||
- число заказов;
|
||
- максимальный `updated_at`.
|
||
|
||
День ODS вычисляется в `Europe/Samara`, как `order_date` в DDS. Область
|
||
проверки - объединение дней обеих сторон, включая день, который полностью
|
||
пропал из одного слоя. Перед сравнением приведите времена к одному часовому
|
||
поясу. `reference_rows` - число заказов ODS, `actual_rows` - число заказов DDS,
|
||
а `failed_rows` равен единице для дня, где расходится хотя бы одно значение.
|
||
|
||
Обе стороны распределены по `order_id`. Дневные агрегаты соедините через
|
||
`GLOBAL FULL OUTER JOIN` с `join_use_nulls = 1`, как в существующих сверках.
|
||
|
||
Если разбор ODS и сборка DDS одинаково потеряют строку, слои останутся равны
|
||
по этой проверке. Общая потеря при разборе проявится в отдельном балансе по
|
||
`_load_id` из [следующего задания](5-2-order-parsing.md).
|
||
|
||
## Получите красный и зеленый результаты
|
||
|
||
Сначала запустите `dq_check` на нетронутых данных. Обе новые задачи должны
|
||
завершиться успешно, а в `dm.dq_summary_v` у обоих имен не должно быть
|
||
нарушенных дней.
|
||
|
||
Выберите оплаченный заказ из последнего слепка с непустым массивом позиций и
|
||
сохраните его `order_id` и `order_date`. Подключитесь к первой ноде из DBeaver
|
||
по [инструкции из README](../../README.md#подключиться-из-dbeaver) под
|
||
пользователем `etl`. Прибавьте одну копейку к `items_total` этого заказа в
|
||
локальной таблице `dds.order_rep` на всем кластере:
|
||
|
||
```sql
|
||
ALTER TABLE dds.order_rep ON CLUSTER clickstream_cluster
|
||
UPDATE items_total = items_total + toDecimal64(0.01, 2)
|
||
WHERE order_id = '<order_id>'
|
||
SETTINGS mutations_sync = 2;
|
||
```
|
||
|
||
Затем запустите только `dq_check`. Задача `order_items_total` должна
|
||
завершиться ошибкой, а ее журнал - назвать выбранный заказ.
|
||
`purchase_vs_orders_vs_dds` тоже даст красный результат: сохраненная витрина
|
||
содержит прежний `items_total`.
|
||
|
||
Запустите `etl_pipeline`. Текущее окно заказов будет заново собрано из ODS, и
|
||
исходная строка вернется в DDS. Повторный `dq_check` должен дать зеленый
|
||
результат для нового сторожа.
|
||
|
||
Теперь удалите тот же заказ из `dds.order_rep` на всем кластере:
|
||
|
||
```sql
|
||
ALTER TABLE dds.order_rep ON CLUSTER clickstream_cluster
|
||
DELETE WHERE order_id = '<order_id>'
|
||
SETTINGS mutations_sync = 2;
|
||
```
|
||
|
||
До восстановления снова запустите только `dq_check`. Красный результат нужен
|
||
для `orders_vs_ods`, `purchase_vs_orders_vs_dds` и `revenue_daily_vs_dds` на
|
||
дне оплаченного заказа. Затем запустите `etl_pipeline` и повторите проверку.
|
||
Если `orders_vs_ods` не обнаружил удаление, исправьте запрос до следующего
|
||
задания.
|
||
|
||
## Как проверить себя
|
||
|
||
В `dm.dq_summary_v` должны быть зелены обе новые и пять прежних проверок:
|
||
|
||
```sql
|
||
SELECT check_name, sum(failed_rows) AS failed_rows, groupUniqArray(status)
|
||
FROM dm.dq_summary_v
|
||
GROUP BY check_name
|
||
ORDER BY check_name;
|
||
```
|
||
|
||
Проверка суммы должна охватывать каждый день `dds.order_v`. Сверка слоев
|
||
должна охватывать объединение дней ODS и DDS, включая дни только одной стороны.
|
||
|
||
## Когда можно остановиться
|
||
|
||
После двух восстановлений выбранный заказ снова совпадает с ODS. Все семь
|
||
проверок дают зеленый результат. В таком состоянии можно закончить работу или
|
||
перейти к разбору заказов, оставив модельное время на паузе.
|
||
|
||
## Приемка
|
||
|
||
- [ ] две новые проверки встроены в `dq_check` и публикуют дневные строки по
|
||
принятому договору;
|
||
- [ ] каждая проверка дала красный результат на своей управляемой ошибке;
|
||
- [ ] журналы упавших задач содержали ограниченную диагностику;
|
||
- [ ] `etl_pipeline` восстановил обе измененные строки DDS;
|
||
- [ ] `order_items_total`, `orders_vs_ods` и пять прежних DQ зелены;
|
||
- [ ] SQL разбора ODS и сборки DDS на этом шаге не менялся.
|