refactor(orders): SQL приёма вынесен из DAG

- Зачем:
  - преобразования хранилища должны читаться рядом с целевым слоем, а DAG должен показывать оркестрацию.
- Что:
  - запросы забора и разбора заказов разложены по каталогам STG и ODS.
  - общий контракт провода подключён в обе ветви штатным шаблонизатором Airflow.
  - SQL смонтирован во все службы Airflow, а правило раскладки записано в архитектуре.
- Проверка:
  - make lint config-test smoke check-services.
  - airflow tasks render для pull_batch и parse_batch; airflow tasks test для parse_batch.
This commit is contained in:
2026-08-18 23:55:04 +03:00
parent bdcbe48b59
commit a5508bd9ec
8 changed files with 195 additions and 165 deletions
+8 -2
View File
@@ -38,19 +38,25 @@
дёргает его тот, кто положил слепок в топик, — работник пульта мира, — и ждёт
конца прогона. Все строки получают `_load_id`, равный `run_id` Airflow.
`_load_ts` вычисляется при этой записи и дальше переносится без пересчёта.
Запрос забора лежит в [`sql/stg/orders_raw_load.sql`](../../../sql/stg/orders_raw_load.sql):
даг задаёт порядок и параметры, а преобразование остаётся в SQL своего слоя.
Один следующий `task_id` отвечает за весь переход STG → ODS. Внутри него два
последовательных `INSERT SELECT` читают неизменный срез по `_load_id`: первый
пишет годные строки в `ods.order_snapshot`, второй — брак в
`ods.order_snapshot_errors`. Транзакции между запросами нет. При частичном сбое
Airflow повторяет весь `task_id`; одинаковые исходные строки и служебные метки
не вычисляются заново.
не вычисляются заново. Запросы лежат рядом с целями:
[`order_snapshot_load.sql`](../../../sql/ods/order_snapshot_load.sql) и
[`order_snapshot_errors_load.sql`](../../../sql/ods/order_snapshot_errors_load.sql).
Условия запросов взаимодополняющие: один общий предикат определяет брак, а
годная ветвь использует его буквальное отрицание. Все функции предиката
возвращают результат без исключения, а сам предикат всегда заканчивается в
`true` или `false`, не в `NULL`. Постоянный классификатор между STG и ODS для
этого не нужен.
этого не нужен. Обе ветви включают один файл
[`_order_wire_contract.sql`](../../../sql/ods/_order_wire_contract.sql), поэтому
предикат нельзя случайно исправить только в одной из них.
## Граница строгого приёма
+18 -2
View File
@@ -414,7 +414,22 @@ kafka_offset)`: смотрят такую таблицу от класса, а
разрастается до имени отдельного поля. Точная граница приёма — в
[спецификации заказов](orders/ingestion.md).
## Раскладка DDL
## Раскладка SQL
Исполняемые дагами запросы лежат по правилу
`sql/<слой>/<объект>_<роль>.sql`. Слой — слой цели запроса: забор заказов в
сырьё живёт в `sql/stg/orders_raw_load.sql`, две ветви разбора — в `sql/ods/`.
Даг называет файлы, передаёт параметры и задаёт порядок выполнения, но не
хранит текст преобразований. Общий фрагмент начинается с подчёркивания: файл
`sql/ods/_order_wire_contract.sql` сам не исполняется, его включают обе ветви
разбора.
Airflow читает и собирает эти файлы штатным шаблонизатором при исполнении
задачи. Каталог `sql/` смонтирован во все его службы только для чтения. Поэтому
обработчик дагов не зависит от наличия SQL на машине при разборе Python, а
планировщик видит те же файлы при исполнении задачи.
### DDL
Файлы лежат в `sql/ddl/` и применяются по порядку имён. Сначала все статичные
объекты, потом матвью — тогда к моменту создания матвью её цель уже существует.
@@ -488,7 +503,8 @@ ODS. Второе: матвью приёма создаётся последне
| ODS | `ods.order_snapshot_errors_rep` / `_dist` | строки слепка, не прошедшие строгий приём |
Матвью разбора у заказов нет: срез сырья раскладывают по этим двум целям два
`INSERT SELECT` шага `parse_batch` в даге `orders_ingest`.
`INSERT SELECT` шага `parse_batch` из файлов `sql/ods/order_snapshot_load.sql`
и `sql/ods/order_snapshot_errors_load.sql`.
Слои DDS и DM появляются на следующих этапах; их состав задан разделом 7
мастер-спеки и переносится сюда по мере постройки.