Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bf61d8f74 | ||
|
|
999a8d08bf | ||
|
|
d3f2f42e38 | ||
|
|
a8148c8afa | ||
|
|
f053fe1e35 | ||
|
|
ab97d6a535 |
No files matched your search
@@ -74,6 +74,11 @@
|
|||||||
- `make clean` удаляет тома — предупреждайте студентов, что данные пропадут.
|
- `make clean` удаляет тома — предупреждайте студентов, что данные пропадут.
|
||||||
|
|
||||||
## Для агента (особенности аудитории)
|
## Для агента (особенности аудитории)
|
||||||
|
|
||||||
|
При создании и пересмотре лабораторных сначала прочитайте
|
||||||
|
[развитие лабораторных](docs/design/labs_development.md): аудитория,
|
||||||
|
принятые решения и критерии заданий.
|
||||||
|
|
||||||
- Пишите простыми словами. Добавляйте короткие комментарии к нетривиальной логике.
|
- Пишите простыми словами. Добавляйте короткие комментарии к нетривиальной логике.
|
||||||
- Избегайте больших рефакторингов и сложных паттернов — студенты только начинают.
|
- Избегайте больших рефакторингов и сложных паттернов — студенты только начинают.
|
||||||
- Ошибки и логи — дружелюбные и понятные (лучше с подсказкой «что сделать дальше»).
|
- Ошибки и логи — дружелюбные и понятные (лучше с подсказкой «что сделать дальше»).
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# airflow-dwh-gp-lab
|
# airflow-dwh-gp-lab
|
||||||
|
|
||||||
|
> Этот стенд можно использовать самостоятельно, но он также является курсовой работой
|
||||||
|
> плана обучения [Data Engineering Roadmap](https://github.com/dementev-dev/de-roadmap).
|
||||||
|
> Нужна помощь? Обратитесь к автору материалов — ментору [@dementev_dev](https://t.me/dementev_dev).
|
||||||
|
|
||||||
Учебный стенд для лабораторных по Data Engineering: **Airflow** оркестрирует загрузку данных из демо‑БД
|
Учебный стенд для лабораторных по Data Engineering: **Airflow** оркестрирует загрузку данных из демо‑БД
|
||||||
**bookings** (Postgres) в **Greenplum**.
|
**bookings** (Postgres) в **Greenplum**.
|
||||||
|
|
||||||
@@ -95,6 +99,12 @@ SELECT COUNT(*) FROM dm.route_performance;
|
|||||||
|
|
||||||
Подробнее про логику DAG и проверки — `docs/bookings_to_gp_stage.md`.
|
Подробнее про логику DAG и проверки — `docs/bookings_to_gp_stage.md`.
|
||||||
|
|
||||||
|
7) Изучите готовую реализацию:
|
||||||
|
|
||||||
|
Весь пайплайн `STG → ODS → DDS → DM` на этой ветке полностью реализован.
|
||||||
|
Переходите к [навигатору по реализации](docs/assignment/README.md) —
|
||||||
|
там описана структура, ключевые паттерны и рекомендуемый порядок изучения.
|
||||||
|
|
||||||
## DAG-и в стенде
|
## DAG-и в стенде
|
||||||
|
|
||||||
Основные (для потока bookings → DWH):
|
Основные (для потока bookings → DWH):
|
||||||
|
|||||||
@@ -44,11 +44,11 @@
|
|||||||
|
|
||||||
> Делаем пока полный пайплайн работает — можно сверяться с реальными данными.
|
> Делаем пока полный пайплайн работает — можно сверяться с реальными данными.
|
||||||
|
|
||||||
- [ ] Создать `docs/assignment/analyst_spec.md`
|
- [x] Создать `docs/assignment/analyst_spec.md`
|
||||||
- [ ] Для каждой таблицы-задания: имя, описание, поля, маппинг,
|
- [x] Для каждой таблицы-задания: имя, описание, поля, маппинг,
|
||||||
бизнес-правила, тип SCD, гранулярность, distribution key
|
бизнес-правила, тип SCD, гранулярность, distribution key
|
||||||
- [ ] Для dim_routes (SCD2): пошаговый алгоритм текстом, формула hashdiff
|
- [x] Для dim_routes (SCD2): пошаговый алгоритм текстом, формула hashdiff
|
||||||
- [ ] Рекомендуемый порядок выполнения
|
- [x] Рекомендуемый порядок выполнения
|
||||||
|
|
||||||
### Этап 3. Валидационный DAG
|
### Этап 3. Валидационный DAG
|
||||||
|
|
||||||
@@ -57,27 +57,26 @@
|
|||||||
|
|
||||||
> Делаем и тестируем на полных данных, пока ничего не удалено.
|
> Делаем и тестируем на полных данных, пока ничего не удалено.
|
||||||
|
|
||||||
- [ ] Создать `airflow/dags/bookings_validate.py`
|
- [x] Создать `airflow/dags/bookings_validate.py`
|
||||||
- [ ] Создать SQL-скрипты в `sql/validate/`
|
- [x] Создать SQL-скрипты в `sql/validate/`
|
||||||
- [ ] Таски по слоям: STG, ODS, DDS, DM
|
- [x] Таски по слоям: STG, ODS, DDS, DM
|
||||||
- [ ] Дружелюбные сообщения об ошибках с подсказками
|
- [x] Дружелюбные сообщения об ошибках с подсказками
|
||||||
- [ ] Протестировать на работающем стенде
|
- [x] Протестировать на работающем стенде
|
||||||
|
|
||||||
### Этап 4. Подготовка main и ветка solution
|
### Этап 4. Подготовка main и ветка solution
|
||||||
|
|
||||||
**Инструмент:** Sonnet — удаление файлов и добавление заглушек
|
**Инструмент:** Opus — раскладка по веткам, заглушки, ослабление DQ.
|
||||||
по списку из [assignment_design.md](docs/design/assignment_design.md).
|
|
||||||
|
|
||||||
> Финальный этап: всё готово и протестировано, теперь раскладываем по веткам.
|
> Финальный этап: всё готово и протестировано, теперь раскладываем по веткам.
|
||||||
|
> План: `docs/plans/2026-03-12_main-solution-split.md`
|
||||||
|
|
||||||
- [ ] Оставить только эталонный срез (sales_report + цепочка)
|
- [x] Смержить chore/bookings-etl → main
|
||||||
- [ ] Убрать реализации таблиц-заданий (airplanes, seats, routes в STG/ODS;
|
- [x] Общие правки на main (документация, TODO.md)
|
||||||
dim_airplanes, dim_passengers, dim_routes в DDS; 4 витрины DM)
|
- [x] Создать ветку solution (снимок полного эталона)
|
||||||
- [ ] Добавить TODO-маркеры / заглушки в DAG'ах для студенческих тасков
|
- [x] Main-only правки: заглушки, ослабление DQ, адаптация тестов
|
||||||
- [ ] Обновить `ddl_gp.sql` (убрать `\i` для таблиц-заданий)
|
- [x] Очистка docs на main (удалить внутренние документы)
|
||||||
- [ ] Создать ветку `solution` от main
|
- [x] Верификация main (`make test`, `make lint`)
|
||||||
- [ ] Добавить полные реализации всех таблиц-заданий
|
- [x] Верификация solution (`make test`)
|
||||||
- [ ] Проверить, что всё работает end-to-end на обеих ветках
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
- [Архитектурные решения (ADR)](design/architecture_review.md)
|
- [Архитектурные решения (ADR)](design/architecture_review.md)
|
||||||
- [PRD: стратегия курсовой](design/PRD.md)
|
- [PRD: стратегия курсовой](design/PRD.md)
|
||||||
- [Дизайн задания](design/assignment_design.md)
|
- [Дизайн задания](design/assignment_design.md)
|
||||||
|
- [Развитие лабораторных: аудитория, решения и критерии заданий](design/labs_development.md)
|
||||||
|
|
||||||
## Справочники (`reference/`)
|
## Справочники (`reference/`)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# План: Онбординг студента + маркетинг (v4)
|
||||||
|
|
||||||
|
> Статус: выполнен (2026-03-13)
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Ментор опционален. Студент, клонировав main, проходит «Быстрый старт» — и застревает:
|
||||||
|
нет явного «что дальше», ссылка на задания спрятана, validate DAG не объяснён, нет маркетинга.
|
||||||
|
На main есть противоречия: docs и DAG-docstrings говорят «все реализовано»,
|
||||||
|
фактически стоят заглушки `SELECT 1;`.
|
||||||
|
|
||||||
|
## Что было сделано
|
||||||
|
|
||||||
|
### На main (10 файлов)
|
||||||
|
|
||||||
|
1. **README.md** — маркетинг-баннер, шаг 6 (только эталонные таблицы), шаг 7 → задания.
|
||||||
|
2. **docs/assignment/README.md** — полный гид студента (эталон → ТЗ → заглушки → validate).
|
||||||
|
3. **docs/assignment/analyst_spec.md** — DAG-интеграция: «таски уже подключены, DAG менять не нужно».
|
||||||
|
4. **docs/bookings_to_gp_dm.md** — пометки заглушек, адаптация секций проверки.
|
||||||
|
5. **docs/bookings_to_gp_ods.md** — пометки заглушек airplanes/seats.
|
||||||
|
6. **docs/bookings_to_gp_dds.md** — пометки заглушек dim_routes/dim_passengers/dim_airplanes.
|
||||||
|
7-10. **4 DAG docstrings** — эталон vs задания (заглушки).
|
||||||
|
|
||||||
|
### На solution (follow-up)
|
||||||
|
|
||||||
|
- README.md — маркетинг-баннер + шаг 7 (solution-specific: «Изучите готовую реализацию»).
|
||||||
|
- docs/assignment/README.md — навигационный гид по всем паттернам (ODS → DDS → DM).
|
||||||
|
- Этот план архивирован в docs/archive/.
|
||||||
|
|
||||||
|
## Верификация
|
||||||
|
|
||||||
|
- `make test` — passed на обеих ветках.
|
||||||
|
- `make lint` — clean на обеих ветках.
|
||||||
|
- Grep «заглушки/SELECT 1» в README.md и docs/assignment/README.md на solution — не найдено.
|
||||||
@@ -1,17 +1,50 @@
|
|||||||
# Учебные задания
|
# Учебные задания
|
||||||
|
|
||||||
|
> Это ветка `solution` — здесь всё уже реализовано.
|
||||||
|
> Используйте её как справочник, а задания выполняйте на ветке `main`.
|
||||||
|
|
||||||
|
## Навигатор по реализации
|
||||||
|
|
||||||
|
На этой ветке полностью работает пайплайн `STG → ODS → DDS → DM`.
|
||||||
|
Ниже — рекомендуемый порядок изучения: от простых паттернов к сложным.
|
||||||
|
|
||||||
|
### ODS: загрузка из STG
|
||||||
|
|
||||||
|
| Паттерн | Файл | Что посмотреть |
|
||||||
|
|---------|------|----------------|
|
||||||
|
| TRUNCATE + INSERT (snapshot-справочник) | `sql/ods/airports_load.sql` | Фильтрация по `stg_batch_id`, дедупликация через `ROW_NUMBER()` |
|
||||||
|
| SCD1 UPSERT (транзакционные данные) | `sql/ods/bookings_load.sql` | TEMP TABLE → UPDATE (`IS DISTINCT FROM`) → INSERT, HWM |
|
||||||
|
|
||||||
|
### DDS: измерения и факт
|
||||||
|
|
||||||
|
| Паттерн | Файл | Что посмотреть |
|
||||||
|
|---------|------|----------------|
|
||||||
|
| SCD1 (простое измерение) | `sql/dds/dim_airports_load.sql` | UPSERT с суррогатным ключом |
|
||||||
|
| SCD1 с обогащением | `sql/dds/dim_airplanes_load.sql` | JOIN с `ods.seats` для `total_seats` |
|
||||||
|
| SCD2 (версионирование) | `sql/dds/dim_routes_load.sql` | `hashdiff`, `valid_from`/`valid_to`, денормализация |
|
||||||
|
| Факт с point-in-time JOIN | `sql/dds/fact_flight_sales_load.sql` | SCD2 lookup, защитные LEFT JOIN |
|
||||||
|
|
||||||
|
### DM: витрины
|
||||||
|
|
||||||
|
| Паттерн | Файл | Что посмотреть |
|
||||||
|
|---------|------|----------------|
|
||||||
|
| UPSERT с HWM по датам | `sql/dm/sales_report_load.sql` | Инкрементальная агрегация фактов |
|
||||||
|
| Full Rebuild (AO Column) | `sql/dm/route_performance_load.sql` | TRUNCATE + INSERT, агрегация по SCD2 `route_bk` |
|
||||||
|
| UNION ALL (dual-role dimension) | `sql/dm/airport_traffic_load.sql` | Разворот факта: departure + arrival |
|
||||||
|
| Двухуровневая агрегация | `sql/dm/monthly_overview_load.sql` | Честный `avg_load_factor` через 2 уровня |
|
||||||
|
| Метод затронутых ключей | `sql/dm/passenger_loyalty_load.sql` | HWM по пассажирам, пересчёт полной истории |
|
||||||
|
|
||||||
## Техническое задание
|
## Техническое задание
|
||||||
|
|
||||||
Основной документ: **[analyst_spec.md](analyst_spec.md)** — ТЗ от аналитика
|
Основной документ: **[analyst_spec.md](analyst_spec.md)** — ТЗ от аналитика
|
||||||
с описанием всех таблиц, маппингами, бизнес-правилами и подсказками.
|
с описанием всех таблиц, маппингами, бизнес-правилами и подсказками.
|
||||||
|
|
||||||
## Эталон для изучения
|
## Описания DAG-ов
|
||||||
|
|
||||||
Перед началом работы изучите эталонный срез (витрина `dm.sales_report`
|
- [STG](../bookings_to_gp_stage.md) ·
|
||||||
и вся её цепочка STG → ODS → DDS → DM):
|
[ODS](../bookings_to_gp_ods.md) ·
|
||||||
|
[DDS](../bookings_to_gp_dds.md) ·
|
||||||
- SQL-скрипты: `sql/stg/`, `sql/ods/`, `sql/dds/`, `sql/dm/`
|
[DM](../bookings_to_gp_dm.md)
|
||||||
- DAG-файлы: `airflow/dags/`
|
|
||||||
- [Порядок запуска DAG-ов](../reference/dag_execution_order.md)
|
- [Порядок запуска DAG-ов](../reference/dag_execution_order.md)
|
||||||
|
|
||||||
## Для менторов
|
## Для менторов
|
||||||
|
|||||||
@@ -123,8 +123,12 @@ DQ-проверки факта отражают эту логику:
|
|||||||
> В боевых системах для этого используют «строку-заглушку» (unknown member, SK = 0)
|
> В боевых системах для этого используют «строку-заглушку» (unknown member, SK = 0)
|
||||||
> и отдельный процесс backfill.
|
> и отдельный процесс backfill.
|
||||||
|
|
||||||
**Point-in-time join для SCD2.**
|
**Два пути lookup для аэропортов и маршрутов.**
|
||||||
Для `dim_routes` используется привязка по дате рейса:
|
Аэропорты (`departure_airport_sk`, `arrival_airport_sk`) разрешаются через `ods.routes` →
|
||||||
|
`dim_airports`. Аэропорты вылета/прилёта одинаковы во всех версиях маршрута, поэтому
|
||||||
|
point-in-time логика не нужна — безопасно брать актуальную версию из ODS.
|
||||||
|
|
||||||
|
`route_sk` и `airplane_sk` разрешаются через point-in-time join с SCD2 `dim_routes`:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
LEFT JOIN dds.dim_routes AS rte
|
LEFT JOIN dds.dim_routes AS rte
|
||||||
|
|||||||
@@ -156,18 +156,9 @@ bookings_validate
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Бэклог: идеи для будущих итераций
|
## 6. Развитие лабораторных
|
||||||
|
|
||||||
### PXF-практикум (замена STG-заданий)
|
Перед добавлением или пересмотром заданий прочитайте
|
||||||
|
[заметку о развитии лабораторных](labs_development.md): там зафиксированы
|
||||||
После переноса всего STG-слоя в эталон (см. `docs/plans/2026-03-11_routes-to-reference.md`)
|
аудитория, решение по PXF-практикуму, предложения по обязательному циклу
|
||||||
студенты не практикуются в создании PXF external tables и загрузке сырых данных.
|
и общие критерии подготовки заданий.
|
||||||
Это важный навык для DE — нужно компенсировать отдельным заданием.
|
|
||||||
|
|
||||||
Варианты:
|
|
||||||
- **Новый источник данных**: подключить CSV/JSON-файл (например, справочник городов
|
|
||||||
или курсов валют) через PXF и загрузить в отдельную STG-таблицу
|
|
||||||
- **Мини-задание «подключи внешний справочник»**: студент создаёт PXF external table
|
|
||||||
для существующей таблицы источника и сравнивает результат с эталонной STG
|
|
||||||
- **Лабораторная работа**: отдельное упражнение на создание external table +
|
|
||||||
сравнение форматов (TEXT vs CUSTOM), профилей PXF, обработку типов
|
|
||||||
@@ -168,13 +168,14 @@ DISTRIBUTED BY (ticket_no)
|
|||||||
|
|
||||||
### 3.8. Политика NULL FK в факте
|
### 3.8. Политика NULL FK в факте
|
||||||
|
|
||||||
FK суррогатные ключи разделены на три группы:
|
FK суррогатные ключи разделены на четыре группы по источнику lookup:
|
||||||
|
|
||||||
| Группа | FK | NULL допустим? | Причина |
|
| Группа | FK | Lookup-путь | NULL допустим? | Причина |
|
||||||
|--------|-----|---------------|---------|
|
|--------|-----|-------------|---------------|---------|
|
||||||
| **Обязательные** | `tariff_sk`, `passenger_sk` | Нет | Данные всегда есть в ODS (segments, tickets). NULL = баг загрузки. |
|
| **Обязательные** | `tariff_sk`, `passenger_sk` | `dim_tariffs` / `dim_passengers` (SCD1) | Нет | Данные всегда есть в ODS. NULL = баг загрузки. |
|
||||||
| **Зависят от маршрута** | `route_sk`, `departure_airport_sk`, `arrival_airport_sk`, `airplane_sk` | Нет (в норме) | Маршрут должен быть в ODS. NULL = аномалия данных, DQ предупреждает. |
|
| **Эталонный справочник** | `departure_airport_sk`, `arrival_airport_sk` | `ods.routes` → `dim_airports` | Нет (в норме) | Аэропорты разрешаются через `ods.routes` (эталон), не через `dim_routes`. NULL = аномалия данных, DQ проверяет с порогом 1%. |
|
||||||
| **Зависят от расписания** | `calendar_sk` | Допустим (редко) | `scheduled_departure` может быть NULL в ODS. DQ считает и логирует, но не фейлит. |
|
| **Point-in-time SCD2** | `route_sk`, `airplane_sk` | `dim_routes` (SCD2 lookup) | Нет (в норме) | Point-in-time привязка к версии маршрута на дату рейса. NULL = аномалия, DQ предупреждает. |
|
||||||
|
| **Расписание** | `calendar_sk` | `dim_calendar` | Допустим (редко) | `scheduled_departure` может быть NULL в ODS. DQ считает и логирует. |
|
||||||
|
|
||||||
DQ-проверки явно контролируют каждую группу (см. секцию 6).
|
DQ-проверки явно контролируют каждую группу (см. секцию 6).
|
||||||
|
|
||||||
@@ -459,15 +460,27 @@ WITH fact_src AS (
|
|||||||
JOIN ods.tickets AS tkt ON tkt.ticket_no = seg.ticket_no
|
JOIN ods.tickets AS tkt ON tkt.ticket_no = seg.ticket_no
|
||||||
JOIN ods.bookings AS bkg ON bkg.book_ref = tkt.book_ref
|
JOIN ods.bookings AS bkg ON bkg.book_ref = tkt.book_ref
|
||||||
JOIN ods.flights AS flt ON flt.flight_id = seg.flight_id
|
JOIN ods.flights AS flt ON flt.flight_id = seg.flight_id
|
||||||
|
-- Airport lookup через ods.routes (эталонный справочник).
|
||||||
|
-- Аэропорты вылета/прилёта одинаковы во всех версиях маршрута —
|
||||||
|
-- безопасно брать из ODS без point-in-time логики.
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT route_no, departure_airport, arrival_airport
|
||||||
|
FROM (
|
||||||
|
SELECT route_no, departure_airport, arrival_airport,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY route_no ORDER BY validity DESC) AS rn
|
||||||
|
FROM ods.routes
|
||||||
|
) ranked
|
||||||
|
WHERE rn = 1
|
||||||
|
) AS ods_rte ON ods_rte.route_no = flt.route_no
|
||||||
-- SCD2 point-in-time: версия маршрута, актуальная на дату вылета
|
-- SCD2 point-in-time: версия маршрута, актуальная на дату вылета
|
||||||
LEFT JOIN dds.dim_routes AS rte
|
LEFT JOIN dds.dim_routes AS rte
|
||||||
ON rte.route_bk = flt.route_no
|
ON rte.route_bk = flt.route_no
|
||||||
AND flt.scheduled_departure::DATE >= rte.valid_from
|
AND flt.scheduled_departure::DATE >= rte.valid_from
|
||||||
AND (rte.valid_to IS NULL OR flt.scheduled_departure::DATE < rte.valid_to)
|
AND (rte.valid_to IS NULL OR flt.scheduled_departure::DATE < rte.valid_to)
|
||||||
LEFT JOIN dds.dim_calendar AS cal ON cal.date_actual = flt.scheduled_departure::DATE
|
LEFT JOIN dds.dim_calendar AS cal ON cal.date_actual = flt.scheduled_departure::DATE
|
||||||
LEFT JOIN dds.dim_airports AS dep ON dep.airport_bk = rte.departure_airport
|
LEFT JOIN dds.dim_airports AS dep ON dep.airport_bk = ods_rte.departure_airport -- через ods.routes
|
||||||
LEFT JOIN dds.dim_airports AS arr ON arr.airport_bk = rte.arrival_airport
|
LEFT JOIN dds.dim_airports AS arr ON arr.airport_bk = ods_rte.arrival_airport -- через ods.routes
|
||||||
LEFT JOIN dds.dim_airplanes AS ap ON ap.airplane_bk = rte.airplane_code
|
LEFT JOIN dds.dim_airplanes AS ap ON ap.airplane_bk = rte.airplane_code -- через dim_routes
|
||||||
LEFT JOIN dds.dim_tariffs AS tar ON tar.fare_conditions = seg.fare_conditions
|
LEFT JOIN dds.dim_tariffs AS tar ON tar.fare_conditions = seg.fare_conditions
|
||||||
LEFT JOIN dds.dim_passengers AS pax ON pax.passenger_bk = tkt.passenger_id
|
LEFT JOIN dds.dim_passengers AS pax ON pax.passenger_bk = tkt.passenger_id
|
||||||
LEFT JOIN ods.boarding_passes AS bp
|
LEFT JOIN ods.boarding_passes AS bp
|
||||||
@@ -497,8 +510,9 @@ ANALYZE dds.fact_flight_sales;
|
|||||||
### 5.5. Модель историчности факта
|
### 5.5. Модель историчности факта
|
||||||
|
|
||||||
Dimension SK фиксируются **при INSERT** и не перезаписываются:
|
Dimension SK фиксируются **при INSERT** и не перезаписываются:
|
||||||
- `route_sk` — версия маршрута на дату `scheduled_departure` (point-in-time SCD2 lookup);
|
- `departure_airport_sk`, `arrival_airport_sk` — из `ods.routes` → `dim_airports` (эталонный справочник; аэропорты одинаковы для всех версий маршрута);
|
||||||
- `departure_airport_sk`, `arrival_airport_sk`, `airplane_sk` — из той же версии маршрута;
|
- `route_sk` — версия маршрута на дату `scheduled_departure` (point-in-time SCD2 lookup через `dim_routes`);
|
||||||
|
- `airplane_sk` — из той же point-in-time версии `dim_routes`;
|
||||||
- `calendar_sk`, `tariff_sk`, `passenger_sk` — из текущих SCD1-измерений на момент INSERT.
|
- `calendar_sk`, `tariff_sk`, `passenger_sk` — из текущих SCD1-измерений на момент INSERT.
|
||||||
|
|
||||||
UPDATE факта обновляет только **мутабельные поля**: `is_boarded`, `seat_no`, `price`
|
UPDATE факта обновляет только **мутабельные поля**: `is_boarded`, `seat_no`, `price`
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ graph LR
|
|||||||
ODS_Tickets -->|book_ref, passenger_id| FACT_Sales
|
ODS_Tickets -->|book_ref, passenger_id| FACT_Sales
|
||||||
ODS_Bookings -->|book_date| FACT_Sales
|
ODS_Bookings -->|book_date| FACT_Sales
|
||||||
ODS_Flights -->|schedule, route_no| FACT_Sales
|
ODS_Flights -->|schedule, route_no| FACT_Sales
|
||||||
|
ODS_Routes -->|dep/arr airports| FACT_Sales
|
||||||
ODS_Boarding -->|LEFT JOIN seat_no| FACT_Sales
|
ODS_Boarding -->|LEFT JOIN seat_no| FACT_Sales
|
||||||
|
|
||||||
%% Dimensions to Fact
|
%% Dimensions to Fact
|
||||||
@@ -220,12 +221,12 @@ SK генерация: `MAX(sk) + ROW_NUMBER()` (безопасно при `conc
|
|||||||
|
|
||||||
`dds.fact_flight_sales` — зерно: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`).
|
`dds.fact_flight_sales` — зерно: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`).
|
||||||
|
|
||||||
| FK | Источник | Примечание |
|
| FK | Lookup-путь | Примечание |
|
||||||
|----|----------|------------|
|
|----|-------------|------------|
|
||||||
| `calendar_sk` | `dim_calendar` | Дата вылета |
|
| `calendar_sk` | `dim_calendar` | Дата вылета |
|
||||||
| `departure_airport_sk` | `dim_airports` | Аэропорт вылета |
|
| `departure_airport_sk` | `ods.routes` → `dim_airports` | Аэропорт вылета (эталонный справочник) |
|
||||||
| `arrival_airport_sk` | `dim_airports` | Аэропорт прилёта |
|
| `arrival_airport_sk` | `ods.routes` → `dim_airports` | Аэропорт прилёта (эталонный справочник) |
|
||||||
| `airplane_sk` | `dim_airplanes` | Самолёт |
|
| `airplane_sk` | `dim_routes` → `dim_airplanes` | Самолёт (point-in-time SCD2) |
|
||||||
| `tariff_sk` | `dim_tariffs` | Тариф |
|
| `tariff_sk` | `dim_tariffs` | Тариф |
|
||||||
| `passenger_sk` | `dim_passengers` | Пассажир |
|
| `passenger_sk` | `dim_passengers` | Пассажир |
|
||||||
| `route_sk` | `dim_routes` | Версия маршрута (SCD2, point-in-time) |
|
| `route_sk` | `dim_routes` | Версия маршрута (SCD2, point-in-time) |
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# Развитие лабораторных работ
|
||||||
|
|
||||||
|
Рабочая заметка для автора программы, 22 сентября 2026 года.
|
||||||
|
Документ хранится в ветке `solution`; оценка программы и ссылки
|
||||||
|
на действующие задания и код относятся к учебной ветке `main`.
|
||||||
|
Приоритет — закрыть пробелы обязательного цикла. Принятое решение о новой
|
||||||
|
лабораторной записано отдельно от предложений, которые еще обсуждаются.
|
||||||
|
Действующий маршрут и требования находятся в [учебных заданиях](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/assignment/README.md)
|
||||||
|
и [ТЗ](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/assignment/analyst_spec.md).
|
||||||
|
|
||||||
|
## Аудитория и цель
|
||||||
|
|
||||||
|
Студент хорошо изучил PostgreSQL и прошел 16-часовой курс Greenplum от Яндекса.
|
||||||
|
Теперь ему нужна практика применения этих знаний в работе Data Engineer.
|
||||||
|
Стенд служит учебным пособием, похожим на небольшое хранилище компании:
|
||||||
|
здесь есть источники, связанные слои данных, загрузки и потребители.
|
||||||
|
|
||||||
|
Программа должна дать опыт подключения источника, построения загрузок
|
||||||
|
и моделей, оркестрации, проверки результата и восстановления после ошибок.
|
||||||
|
Знакомые SQL-инструменты студент применяет к инженерным задачам и объясняет
|
||||||
|
свои решения на данных. Разнообразие работ определяется новыми действиями
|
||||||
|
и решениями; дополнительная витрина полезна, если она дает такой опыт.
|
||||||
|
|
||||||
|
Подготовка по Airflow пока не задана. Первые необходимые действия в UI
|
||||||
|
нужно сопровождать короткой инструкцией или ссылкой на нее. Самостоятельное
|
||||||
|
написание задач и зависимостей вводится явно в соответствующей работе.
|
||||||
|
|
||||||
|
## Метод и рост самостоятельности
|
||||||
|
|
||||||
|
Стенд учебный: его можно ломать и восстанавливать. Готовая реализация
|
||||||
|
позволяет сначала увидеть работающую цепочку, а затем самостоятельно
|
||||||
|
реализовать выбранный участок. Полнота эталона при первом запуске
|
||||||
|
не ограничивает состав лабораторных.
|
||||||
|
|
||||||
|
Этот прием уже используется в
|
||||||
|
[программе Clickstream](https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/specs/2026-09-01-labs-program.md):
|
||||||
|
при «вычитании» студент удаляет готовую реализацию и пишет свою;
|
||||||
|
при работе «собери рядом» сравнивает свою реализацию с работающей;
|
||||||
|
при расследовании находит и устраняет причину подготовленной ошибки.
|
||||||
|
Можно также расширять стенд новым источником или объектом по ТЗ.
|
||||||
|
|
||||||
|
Границу самостоятельной работы определяет навык: в задании по SQL можно
|
||||||
|
оставить готовый DAG, а в задании по оркестрации — готовые SQL-скрипты.
|
||||||
|
Вычитание дает способ выделить участок для работы; самостоятельность
|
||||||
|
обеспечивают решения, которые студент принимает и проверяет:
|
||||||
|
|
||||||
|
1. В первых работах даны требования к результату, близкий пример и готовая
|
||||||
|
проверка. Студент выбирает реализацию и испытывает ее на заданных случаях.
|
||||||
|
2. Дальше студент обосновывает способ загрузки или устройство таблицы,
|
||||||
|
пишет собственные проверки и показывает, какие ошибки они обнаруживают.
|
||||||
|
3. В работах на диагностику даны симптом и критерий восстановления.
|
||||||
|
Студент сам находит причину, выбирает исправление и подтверждает результат.
|
||||||
|
|
||||||
|
Так помощь постепенно сокращается, а ответственность растет.
|
||||||
|
Заранее оставлять заглушки во всех учебных участках не требуется.
|
||||||
|
В частности, зависимость ODS от общего STG-батча объясняет
|
||||||
|
[готовый STG в текущей программе](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/assignment/analyst_spec.md#почему-stg-слой-уже-реализован),
|
||||||
|
но позволяет временно разобрать его в отдельной работе.
|
||||||
|
|
||||||
|
## Принято: прием справочника входит в обязательную программу
|
||||||
|
|
||||||
|
Добавить лабораторную **«Восстановите прием справочника из источника»**.
|
||||||
|
Она закрывает практику подключения через PXF и загрузки полного снимка
|
||||||
|
в STG, которые сейчас даны готовыми.
|
||||||
|
|
||||||
|
Предлагаемый объем: восстановить внешнюю таблицу для существующего
|
||||||
|
справочника Postgres, написать его загрузку в STG и проверить передачу
|
||||||
|
полей, ключей и меток батча. Студент должен различить повтор загрузки
|
||||||
|
в том же батче и получение нового снимка. Результат должен снова
|
||||||
|
участвовать в действующей цепочке 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).
|
||||||
|
|
||||||
|
## Покрытие обязательного цикла
|
||||||
|
|
||||||
|
Оценка основана на текущем ТЗ и коде. Наличие готового примера или главы
|
||||||
|
для чтения учитывается отдельно от самостоятельной реализации.
|
||||||
|
|
||||||
|
| Участок | Что студент делает сейчас | Что стоит добавить |
|
||||||
|
|---|---|---|
|
||||||
|
| PXF и снимок STG | Запускает готовую цепочку | Принятая лабораторная по приему справочника |
|
||||||
|
| Инкремент STG | Использует готовый отбор по дате источника | Самостоятельную загрузку новых данных и проверку границы отбора |
|
||||||
|
| Снимки ODS | Реализует `airplanes`, `seats` и их DQ | Сохранить задания; использовать их после знакомства с приемом данных |
|
||||||
|
| Инкремент ODS | Использует готовые загрузки | Обработку новых ключей и изменений уже полученных строк |
|
||||||
|
| Измерения DDS | Реализует SCD1 и SCD2 | Сохранить существующий блок |
|
||||||
|
| Факт DDS | Читает пример и запускает готовую сборку | Самостоятельную сборку факта с ключами измерений |
|
||||||
|
| Витрины DM | Реализует четыре витрины с разными способами пересчета | Сохранить существующий блок; сначала закрыть пробелы ниже DM |
|
||||||
|
| DDL и физическая организация | Получает готовые таблицы, типы и ключи распределения | Написать DDL одного объекта и проверить выбор распределения опытом; предложение раскрыто ниже |
|
||||||
|
| Airflow | Запускает готовые DAG; задачи уже подключены к SQL | Самостоятельное включение загрузок и проверок в DAG |
|
||||||
|
| DQ и повторяемость | Пишет `*_dq.sql`; для ODS проверяет повтор; пользуется `bookings_validate` | Обязательные опыты на обнаружение ошибки, идемпотентность и восстановление после сбоя |
|
||||||
|
|
||||||
|
## Предложения по оставшимся пробелам
|
||||||
|
|
||||||
|
Следующие изменения пока не утверждены как обязательные лабораторные.
|
||||||
|
|
||||||
|
1. **Загрузите новые и измененные данные: STG → ODS.** Кандидат —
|
||||||
|
`bookings`: [прием в STG](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/sql/stg/bookings_load.sql) и
|
||||||
|
[обновление ODS](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/sql/ods/bookings_load.sql). Проверить первую
|
||||||
|
загрузку, следующую пачку, повтор и отсутствие новых данных.
|
||||||
|
Для изменения существующего ключа подготовить новую версию в STG:
|
||||||
|
текущий отбор источника по `book_date` сам по себе не подхватывает
|
||||||
|
исправления старых бронирований. Различие времени события и времени
|
||||||
|
загрузки — часть учебного результата. Разделение на две работы зависит
|
||||||
|
от объема самостоятельного SQL.
|
||||||
|
2. **Соберите факт продаж.** После измерений восстановить
|
||||||
|
[загрузку факта](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/sql/dds/fact_flight_sales_load.sql): сохранить
|
||||||
|
зерно `(ticket_no, flight_id)`, подобрать ключи измерений и версию
|
||||||
|
маршрута на дату рейса, обработать обновления и повторный запуск.
|
||||||
|
Проверить покрытие ключей, значения и отсутствие размножения строк.
|
||||||
|
Простого совпадения количества строк недостаточно.
|
||||||
|
3. **Организуйте загрузку в Airflow и восстановите ее после сбоя.**
|
||||||
|
Использовать уже написанный SQL, самостоятельно связать задачи загрузки
|
||||||
|
и DQ. На подготовленном входе вызвать сбой, определить состояние данных
|
||||||
|
и завершить обработку повторным запуском. Генератор управляется отдельно:
|
||||||
|
его повтор продвигает источник на новый день и меняет условия опыта.
|
||||||
|
4. **Проверьте физическую организацию таблицы в Greenplum.** Предлагается
|
||||||
|
включить в обязательный цикл компактный опыт: написать DDL, сравнить
|
||||||
|
два варианта распределения одних данных, посмотреть строки по сегментам
|
||||||
|
и планы одного запроса, обосновать выбор. Результат запроса должен
|
||||||
|
совпадать в обоих вариантах. Наличие нескольких сегментов и достаточный
|
||||||
|
для наблюдения объем нужно проверить при подготовке работы. Место опыта
|
||||||
|
еще не выбрано; кандидат — задание по факту. Подробную оптимизацию
|
||||||
|
можно развить в факультативах.
|
||||||
|
|
||||||
|
## Предлагаемые переходы между работами
|
||||||
|
|
||||||
|
Порядок обсуждения новых работ: прием справочника → инкремент STG/ODS →
|
||||||
|
сборка факта → оркестрация и восстановление. Между ними остаются
|
||||||
|
существующие задания на снимки ODS, измерения и витрины. Точное место
|
||||||
|
новых работ, включая опыт с распределением, еще обсуждается.
|
||||||
|
|
||||||
|
Для встраивания в маршрут предлагаются следующие границы:
|
||||||
|
|
||||||
|
| Новая работа | Состояние перед началом | Результат для продолжения |
|
||||||
|
|---|---|---|
|
||||||
|
| Прием справочника | Пройден быстрый старт, работает эталонная цепочка до `sales_report`; выбран участок для вычитания | Собственная загрузка получает новый снимок STG, который участвует в общем батче справочников и принимается ODS |
|
||||||
|
| Инкремент STG/ODS | Разобраны прием снимка и существующие задания ODS; подготовлены таблицы и входные пачки | Собственные загрузки дают ожидаемое текущее состояние ODS после всех проверочных случаев |
|
||||||
|
| Сборка факта | Реализованы и проверены измерения DDS | Факт построен собственным SQL, проверены ключи и значения; на нем можно выполнять задания DM |
|
||||||
|
| Оркестрация и восстановление | SQL выбранного участка реализован и проверен | DAG выполняет загрузку и DQ в нужном порядке; после подготовленного сбоя обработка завершается с ожидаемыми данными |
|
||||||
|
|
||||||
|
Готовность оценивается по нужному этапу: на исходной `main` часть таблиц
|
||||||
|
еще является заданием, поэтому проход всех проверок будущих слоев
|
||||||
|
не может быть условием начала первой работы. Действующие особенности
|
||||||
|
запусков и состояния слоев описаны в [порядке выполнения DAG](https://git.dementev.space/ddmitry/airflow-greenplum/src/branch/main/docs/dag_execution_order.md).
|
||||||
|
|
||||||
|
Предложения основаны на чтении исходников; воспроизводимость новых
|
||||||
|
сценариев на стенде еще не проверялась.
|
||||||
|
|
||||||
|
## Как подготовить лабораторную
|
||||||
|
|
||||||
|
Эти критерии общие для обязательных работ и факультативов. Перед написанием
|
||||||
|
инструкции определите:
|
||||||
|
|
||||||
|
- **Навык:** рабочая ситуация, новое действие или решение DE и его отличие
|
||||||
|
от уже пройденных работ.
|
||||||
|
- **Вход:** необходимые задания, состояние таблиц и загрузок, подготовка
|
||||||
|
данных и способ воспроизвести ситуацию.
|
||||||
|
- **Самостоятельная часть:** что студент получает готовым, что пишет
|
||||||
|
или меняет сам и какие решения должен обосновать.
|
||||||
|
- **Результат:** что будет работать после задания и по каким наблюдениям
|
||||||
|
студент сможет это подтвердить.
|
||||||
|
- **Проверочные случаи:** конкретные данные или события, ожидаемый результат
|
||||||
|
каждого случая и способ обнаружить неверное решение.
|
||||||
|
- **Выход:** что сохраняется для следующей работы и как получить нужное
|
||||||
|
состояние стенда после опыта или перерыва.
|
||||||
|
|
||||||
|
Для инкремента получение новых строк из источника и применение уже
|
||||||
|
полученных изменений в ODS проверяются отдельно. Для собственной DQ-проверки
|
||||||
|
нужны отказ на известной ошибке и успех после исправления. Сверка с готовой
|
||||||
|
реализацией подтверждает совпадение поведения; проверки по источнику
|
||||||
|
подтверждают полноту и бизнес-правила.
|
||||||
|
|
||||||
|
Полная пересборка стенда допустима. Адресное восстановление требуется там,
|
||||||
|
где оно само является предметом работы. При пересборке инструкция должна
|
||||||
|
объяснять, как снова применить сохраненные файлы решения и получить
|
||||||
|
состояние для продолжения.
|
||||||
|
|
||||||
|
Лабораторная готова к выдаче, когда все ее проверочные случаи пройдены
|
||||||
|
на стенде из описанного начального состояния, проверки обнаружили
|
||||||
|
предусмотренные ошибки, а заявленный выход позволяет продолжить маршрут.
|
||||||
|
Команды и результаты проверки сохраняются вместе с изменением задания.
|
||||||
|
Текстовая вычитка и проверка ссылок подтверждают только качество документа.
|
||||||
|
|
||||||
|
## Основа для будущих факультативов
|
||||||
|
|
||||||
|
Факультативы расширяют опыт за пределами обязательного набора. При генерации
|
||||||
|
кандидатов используйте общие критерии выше и указывайте, после каких
|
||||||
|
обязательных работ доступен каждый сюжет.
|
||||||
|
|
||||||
|
Направления для последующей генерации: изменения схемы источника,
|
||||||
|
опоздавшие данные, история и пересчеты, углубленная оптимизация запросов
|
||||||
|
Greenplum, свежесть данных, разграничение доступа, новые способы приема.
|
||||||
|
Это исходные темы для отбора, а не утвержденный список заданий.
|
||||||
Reference in new issue
Block a user