feat(sql): явное управление storage и автоматизированный E2E-тест через REST API
- Зачем: - необходимо визуализировать выбор типа хранения (Heap vs Append-Only) для учебных целей. - автоматизировать проверку всей цепочки DWH для исключения ручных ошибок. - сделать процесс отладки прозрачным и наглядным через стандартные инструменты Airflow. - Что: - внедрена клауза WITH (appendonly=...) во все DDL; ODS-справочники переведены на AO Row и TRUNCATE+INSERT. - создан скрипт scripts/e2e_etl.sh для полного прогона ETL (DDL + 2 дня данных) через REST API. - исправлены баги типизации (INTEGER[]), именования полей (amount, passenger_id) и удалены фантомные колонки (contact_data). - обновлен e2e-etl-test-protocol.md: добавлен раздел по отладке, чтению логов и перезапуску задач через API. - исправлены pytest-контракты под новую логику загрузки. - Проверка: - успешный прогон `make e2e-smoke` (полный цикл от очистки до витрины).
This commit is contained in:
@@ -3,20 +3,25 @@
|
||||
Этот документ описывает процедуру полной проверки цепочки ETL: `STG -> ODS -> DDS -> DM`.
|
||||
Цель теста — убедиться в корректности инкрементальной загрузки, работы паттерна `Temporary Table` и механизмов `HWM`.
|
||||
|
||||
**Основной метод взаимодействия — Airflow UI + REST API.** Запускать пайплайны удобнее всего через веб-интерфейс, а вот отлаживать упавшие задачи (читать логи, очищать статус) полезно уметь через REST API. Это приближает опыт к реальной боевой эксплуатации.
|
||||
|
||||
---
|
||||
|
||||
## 1. Подготовка окружения
|
||||
|
||||
Убедитесь, что все сервисы запущены и DDL применен.
|
||||
Убедитесь, что все сервисы запущены и генератор инициализирован.
|
||||
|
||||
```bash
|
||||
make up
|
||||
make bookings-init
|
||||
make ddl-gp
|
||||
```
|
||||
|
||||
### Важно: Настройка дат
|
||||
Для корректного тестирования исторических данных из `demodb` (начинаются с 2017 года), убедитесь, что в DAG-файлах `start_date` установлен в `2017-01-01`.
|
||||
**Доступ к API:**
|
||||
В `docker-compose.yml` включена базовая аутентификация (`basic_auth`).
|
||||
Для curl-запросов используйте учетные данные из вашего `.env` файла (переменные `AIRFLOW_USER` и `AIRFLOW_PASSWORD`, по умолчанию `admin:admin`).
|
||||
|
||||
- **UI:** `http://localhost:8080`
|
||||
- **API Endpoint:** `http://localhost:8080/api/v1`
|
||||
|
||||
---
|
||||
|
||||
@@ -27,52 +32,99 @@ make ddl-gp
|
||||
```bash
|
||||
make dwh-truncate
|
||||
```
|
||||
*(Если вы меняли DDL, лучше полностью пересоздать схемы: `make gp-psql -c "DROP SCHEMA IF EXISTS ods CASCADE; DROP SCHEMA IF EXISTS dds CASCADE; DROP SCHEMA IF EXISTS dm CASCADE; CREATE SCHEMA ods; CREATE SCHEMA dds; CREATE SCHEMA dm;"`)*
|
||||
|
||||
---
|
||||
|
||||
## 3. Этап 1: Загрузка за первый день (2017-01-01)
|
||||
## 3. Этап 1: Создание схем и Загрузка за первый день (Initial Load)
|
||||
|
||||
Выполните последовательный запуск всех DAG для первой порции данных.
|
||||
Рекомендуется запускать слои последовательно, дожидаясь завершения предыдущего.
|
||||
|
||||
### Создание DDL
|
||||
Откройте **Airflow UI** (`http://localhost:8080`) и нажмите кнопку **▶ Play -> Trigger DAG** для DDL-дагов:
|
||||
1. `bookings_stg_ddl`
|
||||
2. `bookings_ods_ddl`
|
||||
3. `bookings_dds_ddl`
|
||||
4. `bookings_dm_ddl`
|
||||
|
||||
### Запуск пайплайна (Day 1)
|
||||
После успешного создания таблиц, запустите DAG загрузки для `bookings_to_gp_stage`.
|
||||
Откройте **Airflow UI** (`http://localhost:8080`), **снимите DAG с паузы** (переключатель слева от названия) и нажмите кнопку **▶ Play -> Trigger DAG**.
|
||||
*(Airflow автоматически сгенерирует `logical_date` и `run_id`, например `manual__2026-03-03T10:00:00+00:00`)*.
|
||||
|
||||
Либо сделайте то же самое через API (без указания даты). **Важно:** при старте стенда все DAG-и находятся на паузе. Чтобы планировщик начал выполнять запущенный вами DAG, его нужно предварительно "разморозить" (unpause):
|
||||
```bash
|
||||
# Загрузка в STG (создает первый батч в источнике)
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_stage 2017-01-01
|
||||
# Снятие с паузы
|
||||
curl -s -X PATCH "http://localhost:8080/api/v1/dags/bookings_to_gp_stage" \
|
||||
--user "${AIRFLOW_USER}:${AIRFLOW_PASSWORD}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"is_paused": false}'
|
||||
|
||||
# Загрузка в ODS (Initial Load)
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_ods 2017-01-01
|
||||
|
||||
# Загрузка в DDS (Initial Load)
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_dds 2017-01-01
|
||||
|
||||
# Загрузка в DM (Initial Load витрины)
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_dm 2017-01-01
|
||||
# Запуск
|
||||
curl -s -X POST "http://localhost:8080/api/v1/dags/bookings_to_gp_stage/dagRuns" \
|
||||
--user "${AIRFLOW_USER}:${AIRFLOW_PASSWORD}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
### Ожидаемые результаты (Day 1)
|
||||
Проверьте наполнение таблиц:
|
||||
- `stg.bookings` и `ods.bookings` должны иметь одинаковое количество строк (>0).
|
||||
- `dm.sales_report` должна содержать агрегированные данные за первый день.
|
||||
### Проверка статуса
|
||||
Следите за графом выполнения в UI. Как только DAG перейдет в статус `success`, поочередно запускайте следующие слои:
|
||||
1. `bookings_to_gp_ods`
|
||||
2. `bookings_to_gp_dds`
|
||||
3. `bookings_to_gp_dm`
|
||||
|
||||
---
|
||||
|
||||
## 4. Этап 2: Проверка инкремента (2017-01-02)
|
||||
## 4. Этап 2: Проверка инкремента
|
||||
|
||||
Эмулируйте появление данных за второй день и проверьте дозагрузку.
|
||||
Эмулируйте появление данных за второй день и проверьте дозагрузку. DAG слоя STG автоматически сгенерирует новый день в базе-источнике перед загрузкой.
|
||||
|
||||
```bash
|
||||
# Генерация данных за 2-й день в базе-источнике
|
||||
make bookings-generate-day
|
||||
|
||||
# Повторный запуск цепочки ETL
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_stage 2017-01-02
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_ods 2017-01-02
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_dds 2017-01-02
|
||||
docker compose exec airflow-scheduler airflow dags test bookings_to_gp_dm 2017-01-02
|
||||
# Повторный запуск цепочки ETL (DAG STG сам сгенерирует новый день)
|
||||
# Снова нажмите "Trigger DAG" в UI для каждого слоя (STG -> ODS -> DDS -> DM).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Финальная верификация (Критерии успеха)
|
||||
## 5. Цикл отладки: Логи и Перезапуск (Clear)
|
||||
|
||||
Если DAG упал, **не нужно пересоздавать стенд с нуля**. Airflow позволяет исправить код и перезапустить только упавшие задачи.
|
||||
|
||||
Для выполнения команд ниже вам понадобится **Run ID** упавшего запуска. Его можно скопировать из UI (вкладка *Graph* -> кликнуть на фон сетки -> вкладка *Details* -> `Run ID`) или получить последним API-запросом:
|
||||
|
||||
```bash
|
||||
# Получить Run ID последнего запуска ODS
|
||||
curl -s "http://localhost:8080/api/v1/dags/bookings_to_gp_ods/dagRuns?order_by=-execution_date&limit=1" \
|
||||
--user "${AIRFLOW_USER}:${AIRFLOW_PASSWORD}" | grep -o '"dag_run_id": "[^"]*"'
|
||||
```
|
||||
|
||||
### Чтение логов через API
|
||||
Подставьте ваш `<RUN_ID>` (например, `manual__2026-03-03T...`) и имя упавшей таски:
|
||||
```bash
|
||||
curl -s "http://localhost:8080/api/v1/dags/bookings_to_gp_ods/dagRuns/<RUN_ID>/taskInstances/<TASK_ID>/logs/1" \
|
||||
--user "${AIRFLOW_USER}:${AIRFLOW_PASSWORD}"
|
||||
```
|
||||
|
||||
### Перезапуск задачи (Clear)
|
||||
1. Прочитайте ошибку в логах.
|
||||
2. Исправьте SQL-файл локально на хосте.
|
||||
3. Очистите состояние упавших задач (`only_failed: true`) в конкретном запуске, передав ваш `<RUN_ID>`:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:8080/api/v1/dags/bookings_to_gp_ods/clearTaskInstances" \
|
||||
--user "${AIRFLOW_USER}:${AIRFLOW_PASSWORD}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"only_failed": true,
|
||||
"reset_dag_runs": true,
|
||||
"dag_run_id": "<RUN_ID>"
|
||||
}'
|
||||
```
|
||||
После этого планировщик подхватит обновленный SQL-код и продолжит выполнение DAG с точки падения. Вы также можете сделать это в UI: клик по упавшей задаче -> кнопка **Clear**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Финальная верификация (Критерии успеха)
|
||||
|
||||
Выполните SQL-запрос для сверки данных:
|
||||
|
||||
@@ -89,15 +141,16 @@ SELECT 'DM ' as layer, COUNT(*) FROM dm.sales_report;
|
||||
```
|
||||
|
||||
**Критерии корректности:**
|
||||
1. **STG == ODS**: Количество строк в `stg.bookings` и `ods.bookings` совпадает (т.к. это SCD1 UPSERT).
|
||||
2. **Инкремент STG**: Количество строк в `stg.bookings` после Day 2 больше, чем после Day 1.
|
||||
3. **Инкремент ODS (Temporary Table)**: В ODS нет дублей. `SELECT book_ref FROM ods.bookings GROUP BY book_ref HAVING COUNT(*) > 1` должен вернуть 0 строк.
|
||||
4. **HWM в DM**: Витрина `sales_report` содержит данные за оба дня. Значение `COUNT(*)` после Day 2 должно вырасти по сравнению с Day 1.
|
||||
5. **Lineage**: Поля `_load_id` и `_load_ts` во всех слоях содержат метки соответствующих запусков.
|
||||
1. **Инкремент STG**: Количество строк в `stg.bookings` после Этапа 2 больше, чем после Этапа 1.
|
||||
2. **Инкремент ODS**: Количество строк в `ods.bookings` выросло. В ODS нет дублей (`SELECT book_ref FROM ods.bookings GROUP BY book_ref HAVING COUNT(*) > 1` должен вернуть 0 строк).
|
||||
3. **ODS Справочники**: Количество строк в `ods.airports` и `ods.routes` не должно меняться между днями (работает паттерн TRUNCATE+INSERT полного снимка).
|
||||
4. **HWM в DM**: Витрина `sales_report` содержит данные за оба дня. Значение `COUNT(*)` после Этапа 2 выросло.
|
||||
5. **Lineage**: Поля `_load_id` и `_load_ts` во всех слоях содержат метки соответствующих запусков (`manual__...`).
|
||||
|
||||
---
|
||||
|
||||
## Типичные ошибки
|
||||
- **Пустые таблицы**: Проверьте, что в `bookings-db` есть данные (`SELECT COUNT(*) FROM bookings.bookings`). Если 0 — сделайте `make bookings-init`.
|
||||
- **Пропуски в ODS**: Убедитесь, что `stg_batch_id` в ODS корректно вычисляется (задача `resolve_stg_batch_id`).
|
||||
- **Дубли в DDS**: Проверьте логику генерации SK в `dds/*_load.sql`.
|
||||
## 7. Зафиксированный опыт (Типичные ошибки)
|
||||
|
||||
- **Рассинхронизация DDL и Load скриптов**: Частая причина падения ODS/DDS — несовпадение имен колонок (например, `amount` vs `segment_amount`) или типов данных (например, `INTEGER[]` vs `TEXT`) между схемой таблицы и запросом загрузки. Внимательно читайте логи задачи.
|
||||
- **Работа с массивами**: При генерации `hashdiff` в Greenplum/PostgreSQL нельзя использовать пустую строку `''` в `COALESCE` для массива. Массив нужно предварительно привести к тексту: `COALESCE(days_of_week::TEXT, '')`.
|
||||
- **Кавычки в psql**: При выполнении ручных проверок через `psql -c "..."` помните, что строковые литералы должны оборачиваться в **одинарные кавычки** (`'text'`), а двойные кавычки (`"text"`) интерпретируются как идентификаторы колонок.
|
||||
|
||||
Reference in New Issue
Block a user