docs(labs): добавлены эталон и протокол лабораторной PXF
This commit is contained in:
1 parent
7bf61d8f74
commit
d105cb75df
9 files changed
+785
-10
No files matched your search
@@ -59,7 +59,7 @@
|
||||
|
||||
## Принято: прием справочника входит в обязательную программу
|
||||
|
||||
Добавить лабораторную **«Восстановите прием справочника из источника»**.
|
||||
Добавлена лабораторная **«Восстановите прием справочника из источника»**.
|
||||
Она закрывает практику подключения через PXF и загрузки полного снимка
|
||||
в STG, которые сейчас даны готовыми.
|
||||
|
||||
@@ -69,11 +69,13 @@
|
||||
в том же батче и получение нового снимка. Результат должен снова
|
||||
участвовать в действующей цепочке ODS → DDS → DM.
|
||||
|
||||
Конкретный справочник, состав удаляемых файлов, место в маршруте и проверки
|
||||
еще предстоит выбрать. Кандидат — `airports`: рядом есть
|
||||
[внешняя и внутренняя таблицы](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/sql/stg/airports_ddl.sql),
|
||||
[загрузка снимка](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/sql/stg/airports_load.sql) и
|
||||
[преобразование в ODS](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/sql/ods/airports_load.sql).
|
||||
В [#9](https://git.dementev.space/ddmitry/airflow-greenplum/issues/9) выбран `airports`;
|
||||
место — после быстрого старта и знакомства с моделью, перед снимками ODS.
|
||||
Студент восстанавливает внешний DDL и загрузку, сохраняя готовыми внутреннюю
|
||||
таблицу, JDBC-конфигурацию, DQ и DAG. Перед реализацией очищает старые снимки
|
||||
только `stg.airports`. Опыты с повторным выполнением, изменением источника и
|
||||
восстановлением описаны в [инструкции main](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/assignment/pxf_airports.md);
|
||||
обоснование и эталон — в [материалах автора](pxf_airports_lab.md).
|
||||
|
||||
## Покрытие обязательного цикла
|
||||
|
||||
@@ -82,7 +84,7 @@
|
||||
|
||||
| Участок | Что студент делает сейчас | Что стоит добавить |
|
||||
|---|---|---|
|
||||
| PXF и снимок STG | Запускает готовую цепочку | Принятая лабораторная по приему справочника |
|
||||
| PXF и снимок STG | Восстанавливает прием `airports`, испытывает повтор, изменение источника и передачу в ODS | Закрыто лабораторной #9 |
|
||||
| Инкремент STG | Использует готовый отбор по дате источника | Самостоятельную загрузку новых данных и проверку границы отбора |
|
||||
| Снимки ODS | Реализует `airplanes`, `seats` и их DQ | Сохранить задания; использовать их после знакомства с приемом данных |
|
||||
| Инкремент ODS | Использует готовые загрузки | Обработку новых ключей и изменений уже полученных строк |
|
||||
@@ -137,7 +139,7 @@
|
||||
|
||||
| Новая работа | Состояние перед началом | Результат для продолжения |
|
||||
|---|---|---|
|
||||
| Прием справочника | Пройден быстрый старт, работает эталонная цепочка до `sales_report`; выбран участок для вычитания | Собственная загрузка получает новый снимок STG, который участвует в общем батче справочников и принимается ODS |
|
||||
| Прием справочника | Пройден быстрый старт, работает эталонная цепочка до `sales_report` | Собственная загрузка проверена; источник восстановлен, новый общий снимок принят ODS и готов для заданий `airplanes` и `seats` |
|
||||
| Инкремент STG/ODS | Разобраны прием снимка и существующие задания ODS; подготовлены таблицы и входные пачки | Собственные загрузки дают ожидаемое текущее состояние ODS после всех проверочных случаев |
|
||||
| Сборка факта | Реализованы и проверены измерения DDS | Факт построен собственным SQL, проверены ключи и значения; на нем можно выполнять задания DM |
|
||||
| Оркестрация и восстановление | SQL выбранного участка реализован и проверен | DAG выполняет загрузку и DQ в нужном порядке; после подготовленного сбоя обработка завершается с ожидаемыми данными |
|
||||
@@ -147,8 +149,9 @@
|
||||
не может быть условием начала первой работы. Действующие особенности
|
||||
запусков и состояния слоев описаны в [порядке выполнения DAG](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/dag_execution_order.md).
|
||||
|
||||
Предложения основаны на чтении исходников; воспроизводимость новых
|
||||
сценариев на стенде еще не проверялась.
|
||||
Оставшиеся предложения основаны на чтении исходников; их воспроизводимость
|
||||
на стенде еще не проверялась. Состояние проверки приема `airports`
|
||||
зафиксировано отдельно в [материалах #9](pxf_airports_lab.md#проверочный-прогон).
|
||||
|
||||
## Как подготовить лабораторную
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Прием airports через PXF: материалы автора
|
||||
|
||||
Лабораторная [#9](https://git.dementev.space/ddmitry/airflow-greenplum/issues/9)
|
||||
из [серии #8](https://git.dementev.space/ddmitry/airflow-greenplum/issues/8).
|
||||
Студент работает по [инструкции в main](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/assignment/pxf_airports.md).
|
||||
Общие принципы программы находятся в [авторской заметке](labs_development.md).
|
||||
|
||||
## Место и самостоятельная часть
|
||||
|
||||
Работа идет после быстрого старта и знакомства с моделью, перед существующими
|
||||
`ods.airplanes` и `ods.seats`. Входом служит прошедшая эталонная цепочка
|
||||
до `dm.sales_report` на учебной `main`; пустые студенческие таблицы не мешают
|
||||
начать работу. Полный `bookings_validate` не является условием входа.
|
||||
|
||||
Выбран `airports`: он небольшой, содержит ключ, многоязычные JSON и координаты,
|
||||
а его готовая загрузка ODS дает наблюдаемый результат преобразования.
|
||||
Студент удаляет внешний DDL из `sql/stg/airports_ddl.sql` и заменяет
|
||||
`sql/stg/airports_load.sql` заглушкой. В базе удаляет внешний объект и все
|
||||
снимки только `stg.airports`. Без очистки прошлые строки могли бы скрыть
|
||||
отсутствие собственной загрузки.
|
||||
|
||||
Внутренний DDL, JDBC-конфигурация и DQ остаются готовыми. Добавлен готовый
|
||||
`lab_pxf_airports`: восемь PostgresOperator выполняют существующие загрузки
|
||||
и проверки четырех справочников. Студент задает
|
||||
состав и порядок полей внешней таблицы, `LOCATION`, затем пишет получение
|
||||
снимка с ключом и метками. Близкий пример — `airplanes`. Физическая организация
|
||||
внутренней таблицы и самостоятельная оркестрация относятся к следующим работам.
|
||||
|
||||
## Устройство урока
|
||||
|
||||
Основной текст строится вокруг собственной загрузки и опытов с данными.
|
||||
Сначала студент пишет SQL по требованиям и близкому примеру, затем
|
||||
предполагает результат повторного выполнения и переименования аэропорта,
|
||||
наблюдает данные и объясняет различия. Передача нового названия в ODS
|
||||
показывает применение его работы в следующем слое.
|
||||
|
||||
Команды подготовки, первые действия Airflow, сверка и восстановление
|
||||
находятся в раскрываемых блоках рядом с соответствующим действием.
|
||||
Проверки остаются готовыми: написание собственной DQ относится к дальнейшим
|
||||
заданиям. Результат студента — два SQL-файла и объяснение опытов; собирать
|
||||
отчет из JSON, Run ID и логов не требуется.
|
||||
|
||||
Обозначения A/B/C, проверки намеренно неверной реализации и полный протокол
|
||||
приемки относятся к авторским материалам. Они подтверждают пригодность
|
||||
лабораторной, но не задают структуру урока. Одного сокращения формулировок
|
||||
было бы недостаточно: оно сохранило бы последовательность сбора доказательств
|
||||
вместо работы с собственной загрузкой. Демонстрационный разбор также
|
||||
не дал бы предусмотренной программой самостоятельной практики.
|
||||
|
||||
## Полное решение
|
||||
|
||||
Эталон уже содержится в этой ветке:
|
||||
|
||||
- [airports_ddl.sql](../../sql/stg/airports_ddl.sql): шесть TEXT-полей,
|
||||
`pxf://bookings.airports_data?PROFILE=JDBC&SERVER=bookings-db`,
|
||||
`FORMAT 'CUSTOM' (formatter='pxfwritable_import')`.
|
||||
- [airports_load.sql](../../sql/stg/airports_load.sql): чтение внешней таблицы,
|
||||
`_load_id = '{{ run_id }}'`, две метки `now()`, защита `NOT EXISTS`
|
||||
по паре `(_load_id, airport_code)`.
|
||||
- [airports_dq.sql](../../sql/stg/airports_dq.sql): базовая обратная связь DAG.
|
||||
- [airports_load.sql для ODS](../../sql/ods/airports_load.sql): выбор батча,
|
||||
извлечение `ru` и замена текущего снимка.
|
||||
|
||||
Для воспроизведения автор может после вычитания вернуть два исходных файла
|
||||
из сохраненной копии. В учебной ветке они также остаются готовыми при первом
|
||||
запуске: вычитание выполняется самим студентом во время работы.
|
||||
Скрывать ответ технически не требуется; самостоятельность проверяется
|
||||
опытами и объяснением результата.
|
||||
|
||||
## Почему нужны эти проверки
|
||||
|
||||
Готовая [сверка в main](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/scripts/check_airports_snapshot.py)
|
||||
экспортирует источник напрямую из Postgres, без внешней таблицы студента.
|
||||
Поэтому неверный `LOCATION` не становится одновременно ошибкой загрузки
|
||||
и ее контрольного источника. В JSON сохраняются все бизнес-поля, а не только
|
||||
выбранное русское имя. Перезапись контрольного файла запрещена.
|
||||
|
||||
В STG проверяются ключи, отсутствие дублей внутри батча, все бизнес-поля,
|
||||
`_load_id`, наличие `_load_ts` и `event_ts`. JSON сравнивается по содержимому,
|
||||
чтобы порядок свойств не влиял на результат. В ODS проверяется вся таблица,
|
||||
русские значения названий и точный STG `_load_id` у каждой строки.
|
||||
Штатная DQ сверяет количество и обязательные поля, поэтому сама по себе
|
||||
не отвергнет старое непустое название с правильным числом строк.
|
||||
|
||||
| Случай | Наблюдаемый результат |
|
||||
|---|---|
|
||||
| Вычитание | STG пуст; внешняя таблица отсутствует; независимая сверка отказывает |
|
||||
| A | Все ключи и бизнес-поля совпадают с прямым экспортом источника |
|
||||
| Повтор A | Clear только загрузки аэропортов, затем ее DQ в том же запуске; строки сохраняются, генератор не повторяется |
|
||||
| Изменение источника | Сохранен исходный JSON одного аэропорта; изменен только `ru` |
|
||||
| Неверная старая копия | A или его копия с новым `_load_id` не совпадает с контрольным источником B при равном числе строк |
|
||||
| B | B содержит новое имя; A все еще совпадает с исходным экспортом |
|
||||
| ODS | Все четыре справочника имеют B; resolver без `conf` выбирает B; ODS содержит B и новое имя |
|
||||
| Восстановление C | Источник в точности равен исходному A; новый общий C проходит ODS → DDS → DM |
|
||||
|
||||
Для повторов A используется Clear отдельных задач. A/B/C создаются
|
||||
через `lab_pxf_airports`: повтор снимка не требует нового транзакционного дня.
|
||||
Это стало отдельной границей при подготовке опыта: полные запуски STG
|
||||
привязывали каждый контроль справочника к длительной генерации Bookings.
|
||||
Учебный DAG явно исключает генератор и транзакционные загрузки, но использует
|
||||
те же SQL-файлы снимков. Основной `bookings_to_gp_stage` сохраняет свое поведение.
|
||||
|
||||
Автоматический resolver выбирает последний общий ID по времени готовности.
|
||||
Наличие ID во всех таблицах не доказывает их полноту — нужны DQ и сверка.
|
||||
Передача `stg_batch_id` через `conf` обошла бы автоматический выбор, поэтому
|
||||
в проверочном опыте ODS запускается с пустым `conf`.
|
||||
|
||||
## Выход и переход
|
||||
|
||||
В `stg.airports` остаются A/B/C; старые снимки аэропортов удалены в начале.
|
||||
В остальных справочниках остаются также снимки быстрого старта. Транзакционные
|
||||
таблицы сохраняют данные подготовки. B сохраняет
|
||||
учебное изменение как историю; последний общий C и текущий ODS содержат
|
||||
восстановленные значения. Источник восстанавливается из постоянной таблицы
|
||||
`public.lab9_airport_original`, поэтому закрытие psql не теряет копию.
|
||||
Повтор UPDATE восстановления безопасен; таблица удаляется только после сверки.
|
||||
|
||||
Следующая работа берет общий C для `ods.airplanes` и `ods.seats`, без нового
|
||||
STG. Затем сохраняется прежний маршрут измерений и витрин. Работы #10–#14
|
||||
остаются отдельными задачами серии; эта лабораторная не добавляет их заглушки
|
||||
и не меняет текущие требования к ним.
|
||||
|
||||
## Проверочный прогон
|
||||
|
||||
Команды, версии и наблюдения сохраняются в
|
||||
[протоколе прогона](../reports/2026-09-22-pxf-airports.md).
|
||||
Проверять нужно учебную `main`: полный `solution` заполняет дополнительные
|
||||
таблицы и не подтверждает проходимость первой работы с учебными заглушками.
|
||||
@@ -0,0 +1,315 @@
|
||||
# Проверка лабораторной PXF → STG, 22 сентября 2026
|
||||
|
||||
Задача [#9](https://git.dementev.space/ddmitry/airflow-greenplum/issues/9)
|
||||
из [серии #8](https://git.dementev.space/ddmitry/airflow-greenplum/issues/8).
|
||||
[Материалы автора](../design/pxf_airports_lab.md) описывают границу
|
||||
самостоятельной работы и эталонное решение.
|
||||
|
||||
## Среда и начальное состояние
|
||||
|
||||
Проверена учебная ветка на базе `main` (`1dd5838`) с добавленными
|
||||
`lab_pxf_airports` и независимой сверкой. Студенческие заглушки ODS/DDS/DM
|
||||
сохранены. Полная реализация из `solution` в этот стенд не переносилась.
|
||||
Версии: Airflow 2.9.2, Greenplum 6.27.1, Postgres 16.
|
||||
|
||||
```bash
|
||||
make up
|
||||
make bookings-init
|
||||
make ddl-gp
|
||||
```
|
||||
|
||||
После подготовки выполнена обычная цепочка STG → ODS → DDS → DM с Run ID
|
||||
`lab9-baseline-stage`, `lab9-baseline-ods`, `lab9-baseline-dds`,
|
||||
`lab9-baseline-dm`. Все четыре запуска завершились успешно до вычитания.
|
||||
|
||||
В локальном `.env` использованы свободные порты: Airflow 18090, метабаза
|
||||
15433, Bookings 15434, Greenplum 15435. Airflow ограничен одним web-worker,
|
||||
`PARALLELISM=2`, `MAX_ACTIVE_TASKS_PER_DAG=2`, `PARSING_PROCESSES=1`.
|
||||
Эти настройки не внесены в репозиторий; SQL не изменен.
|
||||
|
||||
Первый прогон базового генератора прервался: Postgres сообщил о выходе
|
||||
server process с кодом 2 и переинициализации, после чего источник оказался
|
||||
пустым. `OOMKilled=false`, рестарта контейнера не было; доказанной причины
|
||||
нет. После ограничения ресурсов только своего Airflow источник восстановлен
|
||||
через `make bookings-init`, а базовая цепочка успешно повторена.
|
||||
Чужие контейнеры не изменялись. Дальнейшие снимки справочников получены
|
||||
через `lab_pxf_airports`, без генерации транзакционных дней.
|
||||
|
||||
DAG запускались через `POST /api/v1/dags/{dag_id}/dagRuns` с телом
|
||||
`{"dag_run_id": "указанный в протоколе Run ID", "conf": {}}`.
|
||||
Перед следующим шагом проверялось состояние `success` всего запуска.
|
||||
|
||||
## Вычитание, A и повтор
|
||||
|
||||
A/B/C — обозначения авторского прогона. В ученической инструкции это
|
||||
первая загрузка, новый снимок после переименования и снимок после восстановления.
|
||||
Файлам `source-A.json` и `source-B.json` в задании соответствуют
|
||||
`airports-before.json` и `airports-after.json`.
|
||||
|
||||
```bash
|
||||
mkdir -p data/lab9
|
||||
uv run --no-project scripts/check_airports_snapshot.py export data/lab9/source-A.json
|
||||
```
|
||||
|
||||
Прямой экспорт Postgres: 5501 аэропорт, все шесть бизнес-полей.
|
||||
Внешний DDL удален из `sql/stg/airports_ddl.sql`, загрузка заменена
|
||||
на `SELECT 1;`. В Greenplum:
|
||||
|
||||
```sql
|
||||
DROP EXTERNAL TABLE IF EXISTS stg.airports_ext;
|
||||
TRUNCATE TABLE stg.airports;
|
||||
SELECT COUNT(*) FROM stg.airports;
|
||||
```
|
||||
|
||||
Получено 0 строк. Заглушка успешно выполнилась, но сверка отказала:
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-empty data/lab9/source-A.json
|
||||
```
|
||||
|
||||
Код 1: `Не получены ключи`, всего 5501. Запрос к `stg.airports_ext`
|
||||
получил `relation does not exist`. Старые данные не маскируют вычитание.
|
||||
После возврата эталонных DDL и загрузки применен файл:
|
||||
|
||||
```sql
|
||||
\set ON_ERROR_STOP on
|
||||
\i /sql/stg/airports_ddl.sql
|
||||
SELECT * FROM stg.airports_ext ORDER BY airport_code LIMIT 3;
|
||||
```
|
||||
|
||||
PXF вернул AAA, AAC, AAE с исходными JSON, координатами и часовыми поясами.
|
||||
Успешно выполнен `lab_pxf_airports` с Run ID `lab9-A-stage`.
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-A-stage data/lab9/source-A.json
|
||||
```
|
||||
|
||||
Код 0: 5501 ключ, все бизнес-поля и метки соответствуют требованиям.
|
||||
Повторены только `load_airports_to_stg`, затем `check_airports_dq`
|
||||
в том же запуске через
|
||||
`POST /api/v1/dags/lab_pxf_airports/clearTaskInstances`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dag_run_id": "lab9-A-stage",
|
||||
"task_ids": ["load_airports_to_stg"],
|
||||
"dry_run": true,
|
||||
"only_failed": false,
|
||||
"only_running": false,
|
||||
"include_upstream": false,
|
||||
"include_downstream": false,
|
||||
"include_subdags": false,
|
||||
"include_parentdag": false,
|
||||
"reset_dag_runs": true
|
||||
}
|
||||
```
|
||||
|
||||
Предварительный ответ содержал ровно одну выбранную задачу, затем
|
||||
отправлено то же тело с `dry_run=false`. Для DQ заменен `task_ids`.
|
||||
Повторная сверка A — код 0. При сравнении по ключу все строки, включая
|
||||
метки, совпали до и после повтора. Базовый генератор не повторялся.
|
||||
|
||||
В браузере проверены граф, Run ID и окно Clear task. Подтверждение повтора
|
||||
выполнялось через API. Первоначальный тестовый сценарий ошибочно сравнивал
|
||||
порядок строк MPP-ответа. После сравнения по ключу повтор подтвердился;
|
||||
ошибка не относилась к загрузке. Независимая сверка изначально сопоставляет
|
||||
строки по ключу, экспорт дополнительно сортирует их для файлового сравнения.
|
||||
|
||||
## Изменение источника и неверная копия
|
||||
|
||||
В Postgres выполнено:
|
||||
|
||||
```sql
|
||||
CREATE TABLE public.lab9_airport_original AS
|
||||
SELECT airport_code, airport_name
|
||||
FROM bookings.airports_data ORDER BY airport_code LIMIT 1;
|
||||
|
||||
UPDATE bookings.airports_data AS a
|
||||
SET airport_name = jsonb_set(
|
||||
b.airport_name, '{ru}', to_jsonb((b.airport_name->>'ru') || ' [lab9]'), false
|
||||
)
|
||||
FROM public.lab9_airport_original AS b
|
||||
WHERE a.airport_code = b.airport_code
|
||||
RETURNING a.airport_code, b.airport_name AS original, a.airport_name AS changed;
|
||||
```
|
||||
|
||||
Изменен один ключ AAA: `Анаа` → `Анаа [lab9]`, английское `Anaa`
|
||||
и структура JSON сохранены. Внешняя таблица сразу вернула новое имя,
|
||||
первый снимок сохранил старое.
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py export data/lab9/source-B.json
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-A-stage data/lab9/source-B.json
|
||||
```
|
||||
|
||||
В источнике по-прежнему 5501 строка. Проверка — код 1,
|
||||
`AAA: отличается airport_name`. Затем в Greenplum создана старая копия
|
||||
с новым ID и заполненными метками:
|
||||
|
||||
```sql
|
||||
INSERT INTO stg.airports
|
||||
SELECT airport_code, airport_name, city, country, coordinates, timezone,
|
||||
now(), now(), 'lab9-bad-copy'
|
||||
FROM stg.airports WHERE _load_id = 'lab9-A-stage';
|
||||
```
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-bad-copy data/lab9/source-B.json
|
||||
```
|
||||
|
||||
Вставлено 5501 строк; проверка отвергла копию с кодом 1 и ошибкой имени.
|
||||
Контрольные строки удалены:
|
||||
|
||||
```sql
|
||||
DELETE FROM stg.airports WHERE _load_id = 'lab9-bad-copy';
|
||||
```
|
||||
|
||||
## Снимок B и передача в ODS
|
||||
|
||||
После успешного `lab_pxf_airports`, Run ID `lab9-B-stage`:
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-B-stage data/lab9/source-B.json
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-A-stage data/lab9/source-A.json
|
||||
```
|
||||
|
||||
Обе сверки прошли. Общий батч проверен запросом:
|
||||
|
||||
```sql
|
||||
SELECT 'airports' AS object, _load_id, COUNT(*) FROM stg.airports WHERE _load_id = 'lab9-B-stage' GROUP BY _load_id
|
||||
UNION ALL
|
||||
SELECT 'airplanes', _load_id, COUNT(*) FROM stg.airplanes WHERE _load_id = 'lab9-B-stage' GROUP BY _load_id
|
||||
UNION ALL
|
||||
SELECT 'routes', _load_id, COUNT(*) FROM stg.routes WHERE _load_id = 'lab9-B-stage' GROUP BY _load_id
|
||||
UNION ALL
|
||||
SELECT 'seats', _load_id, COUNT(*) FROM stg.seats WHERE _load_id = 'lab9-B-stage' GROUP BY _load_id;
|
||||
```
|
||||
|
||||
| Таблица | Строк с `lab9-B-stage` |
|
||||
|---|---:|
|
||||
| airports | 5501 |
|
||||
| airplanes | 10 |
|
||||
| routes | 860 |
|
||||
| seats | 1741 |
|
||||
|
||||
До запуска ODS команда ниже отказала из-за прежнего батча и названия,
|
||||
после запуска — прошла:
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-B-stage data/lab9/source-B.json --layer ods
|
||||
```
|
||||
|
||||
Выполнен обычный `bookings_to_gp_ods`, Run ID `lab9-B-ods`, `conf={}`.
|
||||
XCom `return_value` задачи `resolve_stg_batch_id` равен `lab9-B-stage`.
|
||||
Все 5501 строк ODS совпали с ожидаемыми данными и меткой B;
|
||||
название AAA — `Анаа [lab9]`. Выбор через `conf` не подменял resolver.
|
||||
|
||||
## Восстановление и продолжение
|
||||
|
||||
В Postgres дважды выполнен UPDATE восстановления:
|
||||
|
||||
```sql
|
||||
UPDATE bookings.airports_data AS a
|
||||
SET airport_name = b.airport_name
|
||||
FROM public.lab9_airport_original AS b
|
||||
WHERE a.airport_code = b.airport_code
|
||||
RETURNING a.airport_code, a.airport_name;
|
||||
```
|
||||
|
||||
Оба раза `UPDATE 1`, исходный JSON восстановлен. Файловая проверка:
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py export data/lab9/source-C.json
|
||||
cmp data/lab9/source-A.json data/lab9/source-C.json
|
||||
```
|
||||
|
||||
Файлы совпали побайтово. Затем успешно выполнены:
|
||||
|
||||
| DAG | Run ID |
|
||||
|---|---|
|
||||
| lab_pxf_airports | lab9-C-stage |
|
||||
| bookings_to_gp_ods | lab9-C-ods |
|
||||
| bookings_to_gp_dds | lab9-C-dds |
|
||||
| bookings_to_gp_dm | lab9-C-dm |
|
||||
|
||||
Resolver с пустым `conf` выбрал `lab9-C-stage`. Сверки A, B и C со своими
|
||||
источниками, а также всей ODS с C прошли.
|
||||
|
||||
```sql
|
||||
SELECT _load_id, COUNT(*) FROM stg.airports GROUP BY _load_id ORDER BY _load_id;
|
||||
SELECT airport_bk, airport_name FROM dds.dim_airports ORDER BY airport_bk LIMIT 1;
|
||||
SELECT COUNT(*) FROM dds.fact_flight_sales;
|
||||
SELECT COUNT(*) FROM dm.sales_report;
|
||||
```
|
||||
|
||||
В STG остались A/B/C по 5501 строк каждый. В `dds.dim_airports` у AAA
|
||||
снова `Анаа`. Факт содержит 1 608 696 строк, `dm.sales_report` — 25 732.
|
||||
Остальные справочники сохраняют также батчи быстрого старта;
|
||||
транзакционные данные не менялись во время A/B/C. Новый общий снимок
|
||||
пригоден для заданий `ods.airplanes` и `ods.seats`.
|
||||
Полный `bookings_validate` не запускался: будущие задания остаются заглушками.
|
||||
|
||||
## Проверки кода и документации
|
||||
|
||||
- `make lint` — успешно.
|
||||
- `make test` — 14 passed, 15 skipped: десять тестов сверки и четыре
|
||||
SQL-контракта прошли. Четырнадцать smoke-тестов DAG пропущены из-за
|
||||
существующих заглушек Airflow в `conftest.py`; один тест ODS требует
|
||||
явного включения интеграционного прогона.
|
||||
- В настоящем Airflow отдельно проверен DagBag: ошибок импорта нет,
|
||||
восемь задач нового DAG, SQL-шаблоны раскрываются в исходные файлы,
|
||||
зависимости загрузок и DQ верны. REST API не сообщил ошибок импорта.
|
||||
- Проверены локальные ссылки и актуальность карты. В браузере пройден
|
||||
маршрут README → задания → PXF → ODS и ссылка карточки `stg.airports`.
|
||||
- `git diff --check` пройден в учебной и авторской рабочих копиях.
|
||||
|
||||
Учебный прогон выполнен до редакционной перестройки урока. Сценарии,
|
||||
SQL загрузки, DAG и сверка сохранены; изменились порядок объяснений,
|
||||
имена файлов в примерах и показ подсказок. После редактирования проверены
|
||||
296 локальных ссылок учебной ветки и 9 ссылок авторских материалов,
|
||||
синтаксис bash-примеров, актуальность карты (39 объектов, 90 связей,
|
||||
216 ссылок). В локальном HTML-предпросмотре Chromium раскрыты все восемь
|
||||
подсказок, проверено отображение девяти блоков кода и переход к ODS.
|
||||
|
||||
Упрощенное завершение урока использует исходный контроль вместо третьего
|
||||
файла. На сохраненном состоянии прогона отдельно выполнены обе команды:
|
||||
|
||||
```bash
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-C-stage data/lab9/source-A.json
|
||||
uv run --no-project scripts/check_airports_snapshot.py check lab9-C-stage data/lab9/source-A.json --layer ods
|
||||
```
|
||||
|
||||
Обе прошли: 5501 ключ, все бизнес-поля и метки соответствуют требованиям.
|
||||
Полный прогон после редакционной правки не повторялся.
|
||||
|
||||
## Проверка исправления после ревью
|
||||
|
||||
Независимое ревью Standards не обнаружило нарушений. Ревью Spec нашло
|
||||
ловушку при исправлении SQL: уже записанные дубли остаются в батче после
|
||||
добавления `NOT EXISTS`. Инструкция дополнена очисткой только ошибочного
|
||||
`_load_id`, восстановлением снимка и повтором на заполненном снимке.
|
||||
Исправление повторно просмотрено; замечание закрыто.
|
||||
|
||||
В отдельном батче `lab9-review-retry` дважды выполнен INSERT всех строк
|
||||
`stg.airports_ext` с текущими метками. Получено 11002 строки, сверка отказала
|
||||
из-за дублей. После выполнения эталонного `airports_load.sql` с этим же
|
||||
Run ID осталось 11002 строки; сверка снова отказала.
|
||||
|
||||
```sql
|
||||
DELETE FROM stg.airports WHERE _load_id = 'lab9-review-retry';
|
||||
```
|
||||
|
||||
После очистки эталонная загрузка дала 5501 строку и успешную сверку.
|
||||
Ее повтор также прошел. Временный батч затем удален тем же DELETE;
|
||||
состав и количества A/B/C сохранились. Это отдельный SQL-опыт восстановления,
|
||||
а не повтор всего учебного маршрута.
|
||||
|
||||
Перед публикацией `make lint` и `make test` снова прошли
|
||||
(14 passed, 15 skipped). Состояния семи запусков A/B/C и нижних слоев
|
||||
перечитаны через Airflow API: все `success`. Выдержка настоящего лога
|
||||
`check_airports_dq` первого запуска:
|
||||
|
||||
```text
|
||||
[2026-09-22T17:42:17.505+0300] {taskinstance.py:1206} INFO - Marking task as SUCCESS. dag_id=lab_pxf_airports, task_id=check_airports_dq, run_id=lab9-A-stage, execution_date=20260922T144212, start_date=20260922T144216, end_date=20260922T144217
|
||||
[2026-09-22T17:42:17.586+0300] {local_task_job_runner.py:240} INFO - Task exited with return code 0
|
||||
```
|
||||
Reference in new issue
Block a user