feat(dq): добавлен контроль качества конвейера

- Зачем:
  - конвейеру нужна публикуемая сводка текущего качества с красным завершением при нарушении договоров.
- Что:
  - добавлены DDL, публичное представление и пять сверок на естественном деловом зерне.
  - даг dq_check встроен после DM и публикует полные дневные партиции до именованных утверждений.
  - параллелизм DDS, DM и DQ приведён к измеренному лимиту памяти планировщика.
- Проверка:
  - make lint; make config-test; make rebuild-storage.
  - make smoke; make check-clickhouse; make check-services.
  - три семейства проверок покрашены искусственными расхождениями и восстановлены повторным полным прогоном.
This commit is contained in:
ddadmin committed 2026-08-27 07:55:28 -04:00
1 parent 4e60aafc31
commit 26b2e5fea1
17 files changed
+753 -38

No files matched your search

+51 -26
View File
@@ -149,31 +149,58 @@ Distributed-таблица вернула бы каждый непарный з
## `dm.dq_summary`
Зерно — «день данных × проверка»: строка отвечает, каково качество дня
сейчас, а не сколько раз его проверяли, — история прогонов и так живёт в
Airflow. Партиция — день данных; прогон звена DQ заменяет партиции
проверенных дней через `REPLACE PARTITION`. Обещанный спекой перевод с
`TRUNCATE+INSERT` выполняется той же механикой замены, что и у витрин, —
ничего отдельного учить не надо.
Зерно публичного `dm.dq_summary_v` — «день данных × атомарная проверка».
Строка отвечает, каково качество дня сейчас; историю запусков хранит Airflow.
Партиция совпадает с днём данных и заменяется через `REPLACE PARTITION`.
## Будущее звено DQ конвейера
Деловые колонки сводки:
Встанет в `etl_pipeline` после DM ([конвейер](etl.md)) и проверит только
объекты стенда:
- `data_date` — проверяемый день;
- `check_name` — устойчивое имя инварианта;
- `reference_rows` — число деловых ключей на эталонной стороне;
- `actual_rows` — число деловых ключей в проверяемом объекте;
- `failed_rows` — число отсутствующих или различающихся ключей;
- `status` — `pass` при нуле нарушений, иначе `fail`.
- счётная сверка сессий с эталоном `VisitID` — кука × день, эталон читается
из `ods.event_v` ([модель сессии](dds/session.md));
- счётчики классов расхождений против описи мира — по дням с закрытым
окном. Проверка техническая, не урок DQ: её оракул — опись генератора,
а у настоящего магазина оракула нет. Обещание узкое и честное: до
витрин доехал тот мир, который родился, — одна растяжка через весь
путь вместо сторожей на каждом шаге. Верно ли сам мир рождён, она
не видит: опись считает тот же генератор;
- счётные сверки витрин стенда с их DDS-источниками.
Служебные `_load_id` и `_load_ts` называют собравший запуск. Представление
перечисляет колонки явно. Порогов и промежуточного состояния нет.
Результаты ложатся в `dm.dq_summary`. Звено — не только сторож конвейера,
но и экспонат: менти видит, как контроль качества устроен в бою — по
расписанию, с историей, рядом с трансформациями.
## Звено DQ конвейера
Даг `dq_check` стоит в `etl_pipeline` после `dm_transform` и проверяет пять
атомарных договоров:
- `sessions_vs_reference`: ключ — `client_id` внутри дня, эталон —
`uniqExact(VisitID)` из `ods.event_v`, объект — число строк
`dds.session_v`;
- `classes_vs_inventory`: ключ — `mismatch_class`, эталон — счётчик из
`orders` в описи мира, объект — число заказов класса в
`dm.purchase_vs_orders_v`. Проверяются `match`, `cancelled` и
`amount_delta` только для покрытых описью закрытых дней;
- `revenue_daily_vs_dds`: ключ — `product_category` внутри дня. Сравниваются
`orders`, `units`, `revenue` и `aov` с прямым агрегатом `dds.order_v`;
- `purchase_vs_orders_vs_dds`: ключ — `order_id` внутри дня. Все деловые
поля сверки сравниваются с независимой сборкой из `dds.event_v` и
`dds.order_v`;
- `daily_traffic_vs_dds`: ключ — день. `visitors` и `known_users`
сравниваются с прямыми агрегатами `dds.session_v` и
`dds.identity_map_v`.
Область каждой проверки выводится из объединения сторон. Полностью потерянный
день или ключ поэтому не исчезает из результата. Разные ошибки тоже не могут
скомпенсировать друг друга общим итогом: `failed_rows` считается после
сопоставления деловых ключей.
`classes_vs_inventory` — техническая растяжка пути от генератора до DM. Она
подтверждает, что до витрины доехал записанный в описи мир, но не проверяет
сам генератор: у настоящего магазина такого оракула нет.
Пять сборок пишут в общий донор. Только после их успеха даг заменяет все
проверенные дневные партиции, затем именованные задачи читают опубликованную
сводку и падают при `failed_rows > 0`. Расхождение успевает сохраниться до
красного завершения, а ошибка сборки не затирает прежний результат. Красная
задача печатает не больше двадцати нарушенных ключей со значениями сторон;
полный список воспроизводится запросом проверки.
## Даг самопроверки
@@ -188,12 +215,10 @@ Airflow. Партиция — день данных; прогон звена DQ
валидационный даг с группами проверок по слоям давал менти обратную связь
по каждому шагу домашки, не трогая рабочий конвейер.
## Остаётся нарезке
## Остается нарезке
- Имена звена DQ и дага самопроверки.
- Ключи шардирования остальных материализованных витрин — вместе с их
DDL; правило партиции-донора — в [доке хранилища](storage.md).
- Тексты учебных заданий и точный список проверок обоих контуров.
- Имя и состав дага самопроверки домашних заданий.
- Тексты учебных заданий и точный список проверок учебного контура.
## Отклонённые варианты
+5 -5
View File
@@ -13,8 +13,8 @@
Дневной конвейер на стенде один — даг `etl_pipeline`. Своей работы
у него нет: он дёргает слойные даги последовательно и ждёт каждый —
`dds_transform`, затем `dm_transform`. Будущее звено DQ встанет после DM.
Его состав описан в [доке слоя DM](dm.md).
`dds_transform`, затем `dm_transform` и `dq_check`. Состав проверок описан в
[доке слоя DM](dm.md).
Ждущие вызовы сделаны `TriggerDagRunOperator`. Проверка через Context7 по
ближайшей доступной документации Airflow 3.x (версия 3.1.6) подтвердила, что
@@ -28,7 +28,7 @@
```text
world_initialize ─┐
world_live_day ───┼─> etl_pipeline ─> dds_transform ─> dm_transform
world_live_day ───┼─> etl_pipeline ─> dds_transform ─> dm_transform ─> dq_check
запуск рукой ─────┘
world_next_day ─────> источники без ETL
@@ -140,8 +140,8 @@ ODS и ещё не собранный в DDS, затем при необходи
диапазон, включая форму необязательных параметров того же дага, — этап 5
(#7); туда же стык #111 про дату модельного мира и `logical_date`.
- Инкрементальный ETL вместо пересборки — техдолг #2.
- Состав витрин `dm_transform` и звена DQ — [дока слоя DM](dm.md);
имена задач — нарезка этапа 4.
- Состав витрин `dm_transform` и проверок `dq_check` —
[дока слоя DM](dm.md).
## Отклонённые варианты
+3
View File
@@ -606,6 +606,9 @@ SQL-файлы исполняет штатный `SQLExecuteQueryOperator`. Пр
| `dm` | `dm.purchase_vs_orders_v` | публичный договор сверки с `NULL` для отсутствующей стороны |
| `dm` | `dm.daily_traffic_rep` / `_dist` | версии дневных счетчиков трафика |
| `dm` | `dm.daily_traffic_v` | точные текущие счетчики трафика с `FINAL` |
| `dm` | `dm.dq_summary_rep` / `_dist` | текущее качество дня по атомарным проверкам |
| `dm` | `dm.dq_summary_stage_rep` / `_dist` | общий донор дневных партиций DQ-сводки |
| `dm` | `dm.dq_summary_v` | публичная сводка качества дней |
| `dic` | `dic.products_file` | чтец CSV-каталога, общего с генератором |
| `dic` | `dic.products` | словарь товаров поверх подложки |
+2 -2
View File
@@ -514,8 +514,8 @@ Kafka день переигрывается генератором заново:
- `events_enriched_v`, `top_pages_daily_v`, `session_overview_v`,
`dq_errors_daily_v` — переезжают на новую модель без смены роли: источник —
`dds.event_v`, не `ods.event`.
- `dm.dq_summary` переводится с TRUNCATE+INSERT на партиционную замену
(политика «без TRUNCATE»).
- `dm.dq_summary` хранит текущее качество дня и обновляется партиционной
заменой (политика «без TRUNCATE»).
Дашборд Superset получает три новых сюжета: выручка по дням и категориям,
таблица сверки с классами расхождений, пара посетители/известные.