docs(internal): добавлена первая версия PRD для DWH-стенда
- Зачем: - необходим единый документ с требованиями для разработки ETL-процессов. - Что: - создан файл docs/internal/PRD.md с описанием архитектуры, требований к данным и сроков. - Проверка: - просмотр файла docs/internal/PRD.md.
This commit is contained in:
@@ -0,0 +1,393 @@
|
|||||||
|
# PRD: Greenplum Bookings DWH
|
||||||
|
|
||||||
|
> Курсовая работа для курса [DE Roadmap](https://github.com/dementev-dev/de-roadmap).
|
||||||
|
> Статус: **ЧЕРНОВИК v0.1** | Дата: 2026-03-08
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Видение продукта
|
||||||
|
|
||||||
|
**Greenplum Bookings DWH** — учебный стенд, на котором студент самостоятельно строит
|
||||||
|
end-to-end ETL-пайплайн: от базы-источника до аналитических витрин.
|
||||||
|
|
||||||
|
Стенд имитирует реальную рабочую задачу Data-инженера:
|
||||||
|
- Есть «боевая» система-источник (bookings-db), в которой каждый день появляются
|
||||||
|
новые данные — как в жизни, без ограниченного объёма.
|
||||||
|
- Есть DWH на Greenplum с классическими слоями (STG → ODS → DDS → DM).
|
||||||
|
- Есть Airflow, оркестрирующий загрузку.
|
||||||
|
- Есть ТЗ от «аналитика» с описанием ожидаемых таблиц и маппингов.
|
||||||
|
|
||||||
|
Студент получает **частично реализованный пайплайн** (эталонный вертикальный срез)
|
||||||
|
и **дореализует остальное** по ТЗ — SQL-скрипты и таски в DAG.
|
||||||
|
|
||||||
|
### Почему именно bookings?
|
||||||
|
|
||||||
|
Домен бронирования авиабилетов выбран не ради предметной области, а благодаря
|
||||||
|
генератору данных: каждый вызов `make bookings-generate-day` создаёт новый день
|
||||||
|
с реалистичным объёмом. Это даёт бесконечный поток инкрементальных данных —
|
||||||
|
как в настоящей production-системе.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Целевая аудитория и пререквизиты
|
||||||
|
|
||||||
|
**Кто:** студенты курса DE Roadmap, дошедшие до раздела «Курсовая работа».
|
||||||
|
|
||||||
|
**Что уже умеют** (к моменту старта):
|
||||||
|
- Git: ветки, PR, merge, GitFlow
|
||||||
|
- SQL: JOIN, CTE, оконные функции, планы запросов, моделирование (3NF, звезда, SCD)
|
||||||
|
- Python: скрипты, pandas, базовое ООП
|
||||||
|
- Docker: запуск контейнеров, логи, docker-compose
|
||||||
|
- Airflow: понятие DAG, операторы, зависимости, UI, логи
|
||||||
|
- Greenplum: распределение по сегментам, skew, EXPLAIN, отличие от Postgres
|
||||||
|
|
||||||
|
**Уровень:** уверенный джун, готовящийся к первым собеседованиям.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Учебные результаты (Learning Outcomes)
|
||||||
|
|
||||||
|
После выполнения курсовой студент умеет:
|
||||||
|
|
||||||
|
1. **Проектировать и реализовывать ETL-пайплайн** по слоям DWH
|
||||||
|
(STG → ODS → DDS → DM) на реальном стеке Airflow + Greenplum.
|
||||||
|
2. **Читать ТЗ от аналитика** (маппинги, описания таблиц) и превращать его
|
||||||
|
в работающий SQL + DAG.
|
||||||
|
3. **Писать идемпотентные загрузки** с инкрементальностью (HWM, batch_id,
|
||||||
|
delete+insert), понимая, почему в Greenplum не используется MERGE.
|
||||||
|
4. **Реализовывать SCD1/SCD2** и объяснять, когда что применяется.
|
||||||
|
5. **Настраивать и проверять Data Quality** — понимает, зачем DQ-проверки
|
||||||
|
и как их встроить в пайплайн.
|
||||||
|
6. **Работать с Greenplum** как с MPP: выбирать distribution key,
|
||||||
|
понимать heap vs AO, читать планы запросов.
|
||||||
|
7. **Оформить проект как портфолио** — репозиторий пригоден для упаковки
|
||||||
|
в резюме как реальный опыт работы с Airflow и Greenplum.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Скоуп
|
||||||
|
|
||||||
|
### В скоупе (In Scope)
|
||||||
|
|
||||||
|
| Компонент | Описание |
|
||||||
|
|-----------------------|-------------------------------------------------------------|
|
||||||
|
| Источник данных | bookings-db (Postgres) с генератором дней |
|
||||||
|
| DWH | Greenplum, 4 слоя: STG, ODS, DDS, DM |
|
||||||
|
| Оркестрация | Apache Airflow (PostgresOperator + SQL-файлы) |
|
||||||
|
| Федеративный доступ | PXF (чтение из Postgres в Greenplum) |
|
||||||
|
| Инфраструктура | Docker Compose (полный стенд в одной команде) |
|
||||||
|
| Data Quality | DQ-проверки, встроенные в DAG |
|
||||||
|
| Документация | README, ТЗ, design docs, naming conventions |
|
||||||
|
|
||||||
|
### Вне скоупа (Out of Scope)
|
||||||
|
|
||||||
|
| Что | Почему |
|
||||||
|
|-----------------------|-------------------------------------------------------------|
|
||||||
|
| Kafka / стриминг | Отдельный стенд в курсе |
|
||||||
|
| BI-инструменты | Фокус на ETL, не на визуализации |
|
||||||
|
| CI/CD | Избыточно для курсовой |
|
||||||
|
| Spark / Trino / dbt | Отдельные стенды в курсе |
|
||||||
|
| Второй источник | Усложнение без пропорциональной учебной ценности |
|
||||||
|
| CSV-пайплайн | Вынести в [airflow-manual](https://github.com/dementev-dev/airflow-manual) |
|
||||||
|
| Облачная инфраструктура | Всё локально, через Docker |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Архитектура стенда
|
||||||
|
|
||||||
|
### Сервисы (Docker Compose)
|
||||||
|
|
||||||
|
```
|
||||||
|
bookings-db (Postgres 16) ──PXF──> Greenplum 6.27
|
||||||
|
├── stg.* (стейджинг)
|
||||||
|
├── ods.* (операционное хранилище)
|
||||||
|
├── dds.* (детальное хранилище)
|
||||||
|
└── dm.* (витрины)
|
||||||
|
|
||||||
|
pgmeta (Postgres 16) ─────────────> Airflow (webserver + scheduler)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Слои DWH
|
||||||
|
|
||||||
|
| Слой | Назначение | Паттерн загрузки | Кол-во таблиц |
|
||||||
|
|------|-----------------------------------|---------------------------|---------------|
|
||||||
|
| STG | Зеркало источника | TRUNCATE + INSERT (batch) | 9 |
|
||||||
|
| ODS | Нормализованное хранилище | SCD1 UPSERT | 9 |
|
||||||
|
| DDS | Измерения + факты (Kimball) | SCD1/SCD2 + fact load | 7 (6D + 1F) |
|
||||||
|
| DM | Аналитические витрины | HWM-инкремент | 5 |
|
||||||
|
|
||||||
|
### Сущности
|
||||||
|
|
||||||
|
| STG / ODS | DDS | DM |
|
||||||
|
|----------------------|--------------------------|-----------------------|
|
||||||
|
| bookings | dim_airports | airport_traffic |
|
||||||
|
| tickets | dim_airplanes | monthly_overview |
|
||||||
|
| airports | dim_passengers | passenger_loyalty |
|
||||||
|
| airplanes | dim_routes (SCD2) | route_performance |
|
||||||
|
| routes | dim_calendar | sales_report |
|
||||||
|
| seats | dim_tariffs | |
|
||||||
|
| flights | fact_flight_sales | |
|
||||||
|
| segments | | |
|
||||||
|
| boarding_passes | | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Педагогическая модель
|
||||||
|
|
||||||
|
### Принцип: «Эталонный срез + ТЗ»
|
||||||
|
|
||||||
|
Студент получает репозиторий, в котором:
|
||||||
|
|
||||||
|
1. **Эталонный вертикальный срез** — полностью реализованная цепочка для одной
|
||||||
|
витрины DM и всех её источников вниз по слоям (STG → ODS → DDS → DM).
|
||||||
|
Это — образец, на который студент ориентируется.
|
||||||
|
|
||||||
|
2. **ТЗ от аналитика** — Markdown-документ с описанием остальных таблиц:
|
||||||
|
маппинги полей, бизнес-правила, ожидаемая гранулярность, тип SCD.
|
||||||
|
|
||||||
|
3. **Частично готовый DAG** — Python-файлы DAG с реализованными тасками
|
||||||
|
эталонного среза. Студент добавляет свои таски по аналогии.
|
||||||
|
|
||||||
|
4. **DQ-проверки** — <!-- TODO: проработать формат --> готовые проверки,
|
||||||
|
которые студент запускает для самоконтроля.
|
||||||
|
|
||||||
|
### Что делает студент
|
||||||
|
|
||||||
|
- Пишет DDL для назначенных таблиц (`*_ddl.sql`)
|
||||||
|
- Пишет SQL-загрузки (`*_load.sql`)
|
||||||
|
- При необходимости пишет DQ-проверки (`*_dq.sql`)
|
||||||
|
- Добавляет таски в существующий DAG
|
||||||
|
- Проверяет результат через DQ и запросы в Greenplum
|
||||||
|
|
||||||
|
### Что студент НЕ делает
|
||||||
|
|
||||||
|
- Не поднимает инфраструктуру с нуля (Docker Compose дан)
|
||||||
|
- Не пишет DAG с нуля (шаблон дан)
|
||||||
|
- Не настраивает Airflow Connections (преднастроены)
|
||||||
|
- Не работает с PXF-конфигурацией (настроен)
|
||||||
|
|
||||||
|
### Выбор эталонного среза
|
||||||
|
|
||||||
|
### Эталонный срез: витрина `sales_report`
|
||||||
|
|
||||||
|
Эталоном выбрана витрина `dm.sales_report` и вся её цепочка вниз по слоям.
|
||||||
|
|
||||||
|
**Почему `sales_report`:**
|
||||||
|
- Покрывает SCD1 (airports, tariffs), HWM-инкремент, fact load
|
||||||
|
- Богатая денормализация — хороший образец для подражания
|
||||||
|
- Средняя сложность — не пугает, но и не тривиальна
|
||||||
|
|
||||||
|
**Эталонные таблицы (даны студенту):**
|
||||||
|
|
||||||
|
| Слой | Таблицы |
|
||||||
|
|------|-----------------------------------------------------------------|
|
||||||
|
| DM | `sales_report` |
|
||||||
|
| DDS | `fact_flight_sales`, `dim_airports` (SCD1), `dim_tariffs` (SCD1), `dim_calendar` |
|
||||||
|
| ODS | `bookings`, `tickets`, `segments`, `flights`, `boarding_passes`, `airports` |
|
||||||
|
| STG | `bookings`, `tickets`, `segments`, `flights`, `boarding_passes`, `airports` |
|
||||||
|
|
||||||
|
**Задание студенту:**
|
||||||
|
|
||||||
|
| Слой | Таблицы | Что нового для студента |
|
||||||
|
|------|-------------------------------------------------------------------|--------------------------------------------------|
|
||||||
|
| STG | `airplanes`, `seats`, `routes` | Практика по аналогии с эталоном |
|
||||||
|
| ODS | `airplanes`, `seats`, `routes` | Практика SCD1 UPSERT по аналогии |
|
||||||
|
| DDS | `dim_airplanes` (SCD1), `dim_passengers` (SCD1), `dim_routes` (SCD2) | **SCD2 — ключевой вызов курсовой** |
|
||||||
|
| DM | `airport_traffic`, `monthly_overview`, `route_performance`, `passenger_loyalty` | Разная сложность (от простой к сложной) |
|
||||||
|
|
||||||
|
**Рекомендуемый порядок выполнения для студента:**
|
||||||
|
1. STG (airplanes, seats, routes) — разминка, по аналогии
|
||||||
|
2. ODS (airplanes, seats, routes) — закрепление UPSERT
|
||||||
|
3. DDS dim_airplanes, dim_passengers (SCD1) — новые измерения
|
||||||
|
4. DDS dim_routes (**SCD2**) — ключевой вызов
|
||||||
|
5. DM airport_traffic — простая витрина, похожа на sales_report
|
||||||
|
6. DM route_performance — TRUNCATE+INSERT, SCD2-агрегация по BK
|
||||||
|
7. DM monthly_overview — двухуровневая агрегация
|
||||||
|
8. DM passenger_loyalty — самая сложная, пересчёт истории
|
||||||
|
|
||||||
|
### SCD2 в задании: подход «рецепт без готового SQL»
|
||||||
|
|
||||||
|
Реализация `dim_routes` (SCD2) — ключевой вызов курсовой. Студент делает это
|
||||||
|
самостоятельно, но ТЗ содержит пошаговую подсказку:
|
||||||
|
|
||||||
|
1. Алгоритм SCD2 текстом (без SQL):
|
||||||
|
- Вычисли `hashdiff` по набору атрибутов (атрибуты перечислены в ТЗ)
|
||||||
|
- Найди строки, у которых `hashdiff` изменился
|
||||||
|
- Закрой старую версию (`valid_to = текущая_дата`)
|
||||||
|
- Вставь новую версию (`valid_from = текущая_дата`, `valid_to = NULL`)
|
||||||
|
2. Формула hashdiff: `md5(concat_ws('|', field1, field2, ...))`
|
||||||
|
3. Ссылка на `naming_conventions.md` (поля `valid_from`, `valid_to`, `hashdiff`)
|
||||||
|
4. Напоминание: полуоткрытый интервал `[valid_from, valid_to)`
|
||||||
|
5. Если застрял — ветка `solution`
|
||||||
|
|
||||||
|
Самостоятельная реализация — ключ к запоминанию. SCD2 — обязательный вопрос
|
||||||
|
на собеседованиях DE, и студент должен уметь объяснить его на основе
|
||||||
|
собственного опыта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6.1. Валидационный DAG (`bookings_validate`)
|
||||||
|
|
||||||
|
Отдельный DAG для самопроверки студента. Запускается вручную в Airflow UI
|
||||||
|
после реализации заданий. Таски сгруппированы по слоям — студент видит,
|
||||||
|
где именно проблема.
|
||||||
|
|
||||||
|
**Примерная структура тасков:**
|
||||||
|
|
||||||
|
```
|
||||||
|
bookings_validate
|
||||||
|
├── validate_stg
|
||||||
|
│ ├── check_stg_airplanes_exists (таблица создана, >0 строк)
|
||||||
|
│ ├── check_stg_seats_exists
|
||||||
|
│ └── check_stg_routes_exists
|
||||||
|
├── validate_ods
|
||||||
|
│ ├── check_ods_airplanes_rowcount (ODS >= STG по кол-ву уникальных BK)
|
||||||
|
│ ├── check_ods_seats_rowcount
|
||||||
|
│ ├── check_ods_routes_rowcount
|
||||||
|
│ └── check_ods_no_null_pks (PK not null)
|
||||||
|
├── validate_dds
|
||||||
|
│ ├── check_dim_airplanes_exists
|
||||||
|
│ ├── check_dim_passengers_exists
|
||||||
|
│ ├── check_dim_routes_scd2 (valid_from/valid_to корректны)
|
||||||
|
│ └── check_dim_routes_no_gaps (нет «дыр» в версиях SCD2)
|
||||||
|
└── validate_dm
|
||||||
|
├── check_airport_traffic_exists
|
||||||
|
├── check_monthly_overview_exists
|
||||||
|
├── check_route_performance_exists
|
||||||
|
└── check_passenger_loyalty_exists
|
||||||
|
```
|
||||||
|
|
||||||
|
**Реализация:** `PostgresOperator` + SQL-скрипты в `sql/validate/`.
|
||||||
|
Каждый SQL-скрипт выполняет SELECT и бросает исключение (через
|
||||||
|
`DO $$ ... RAISE EXCEPTION ... $$`), если проверка не пройдена.
|
||||||
|
Сообщения об ошибках — дружелюбные, с подсказкой что делать дальше.
|
||||||
|
|
||||||
|
**Ключевые проверки:**
|
||||||
|
- Таблицы существуют и содержат данные
|
||||||
|
- PK не содержат NULL
|
||||||
|
- SCD2: `valid_to IS NULL` для текущих версий, нет перекрытий интервалов
|
||||||
|
- Кросс-слойная консистентность (row count ODS vs STG)
|
||||||
|
- DM-витрины содержат данные за загруженные дни
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Формат ТЗ от аналитика
|
||||||
|
|
||||||
|
Файл: `docs/assignment/analyst_spec.md` (или несколько файлов по слоям).
|
||||||
|
|
||||||
|
Для каждой таблицы-задания документ содержит:
|
||||||
|
|
||||||
|
- **Имя таблицы** и целевая схема (stg / ods / dds / dm)
|
||||||
|
- **Описание** — что хранит таблица, бизнес-смысл
|
||||||
|
- **Список полей** с типами и описанием
|
||||||
|
- **Маппинг источников** — откуда берётся каждое поле
|
||||||
|
- **Бизнес-правила и фильтры** (если есть)
|
||||||
|
- **Тип историзации** (SCD1 / SCD2 / snapshot / append)
|
||||||
|
- **Гранулярность** (одна строка = ?)
|
||||||
|
- **Distribution key** (подсказка или задание на выбор)
|
||||||
|
|
||||||
|
Формат — приближен к реальным ТЗ, которые студент встретит на работе.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ветки и workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
main (стартовое состояние)
|
||||||
|
├── Эталонный срез: реализованные таблицы + DAG
|
||||||
|
├── ТЗ от аналитика
|
||||||
|
├── Инфраструктура (Docker, Make, PXF)
|
||||||
|
├── Заглушки / TODO-маркеры для студенческих заданий
|
||||||
|
└── DQ-проверки для самоконтроля
|
||||||
|
|
||||||
|
solution (полное решение)
|
||||||
|
└── Все таблицы реализованы — эталон для самопроверки
|
||||||
|
и подсказка, если студент застрял
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow студента
|
||||||
|
|
||||||
|
1. Форкает репозиторий
|
||||||
|
2. Читает README и ТЗ
|
||||||
|
3. `make up` — поднимает стенд
|
||||||
|
4. `make bookings-init` — инициализирует источник
|
||||||
|
5. Запускает DDL-DAG'и (эталонные таблицы создаются)
|
||||||
|
6. Запускает ETL-DAG'и — эталонный срез работает
|
||||||
|
7. Реализует задания из ТЗ (SQL + таски в DAG)
|
||||||
|
8. Проверяет себя через DQ
|
||||||
|
9. `make bookings-generate-day` — генерирует новый день, проверяет
|
||||||
|
инкрементальность
|
||||||
|
10. Защищает работу перед ментором
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Критерии приёмки курсовой
|
||||||
|
|
||||||
|
### Для студента (самопроверка)
|
||||||
|
|
||||||
|
- [ ] Стенд поднимается (`make up`) без ошибок
|
||||||
|
- [ ] Все DAG'и проходят без failed-тасков
|
||||||
|
- [ ] Данные доезжают от STG до DM
|
||||||
|
- [ ] DQ-проверки проходят на всех реализованных таблицах
|
||||||
|
- [ ] После `make bookings-generate-day` + повторного запуска DAG
|
||||||
|
данные корректно доливаются (инкрементальность работает)
|
||||||
|
|
||||||
|
### Для ментора (ревью + защита)
|
||||||
|
|
||||||
|
- [ ] Код соответствует naming conventions (`docs/internal/naming_conventions.md`)
|
||||||
|
- [ ] SQL идемпотентен (повторный запуск не ломает данные)
|
||||||
|
- [ ] Distribution keys выбраны осмысленно
|
||||||
|
- [ ] Студент может объяснить: почему delete+insert, а не MERGE;
|
||||||
|
разницу SCD1/SCD2; что такое HWM; как работает batch_id
|
||||||
|
- [ ] Код оформлен для портфолио (чистый Git-history, README)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Ограничения и риски
|
||||||
|
|
||||||
|
| Риск / ограничение | Митигация |
|
||||||
|
|--------------------------------------------|-------------------------------------------------|
|
||||||
|
| Стенд тяжёлый (~8-16 GB RAM) | Указать минимальные требования; не утяжелять |
|
||||||
|
| bookings-db генерирует данные медленно | Не добавлять нагрузку; задокументировать ожидание|
|
||||||
|
| Студент может застрять надолго | Ветка `solution` как подсказка; еженедельные встречи |
|
||||||
|
| Greenplum 6.x — устаревающая версия | Для учебных целей достаточно; паттерны переносимы |
|
||||||
|
| PXF нестабилен при холодном старте | Задокументировано в README; healthcheck настроен |
|
||||||
|
|
||||||
|
### Требования к машине студента
|
||||||
|
|
||||||
|
- 2-4 CPU, 8-16 GB RAM, 25-40 GB диска
|
||||||
|
- Linux / WSL2 / macOS
|
||||||
|
- Docker + Docker Compose
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Таймлайн
|
||||||
|
|
||||||
|
| Когда | Что |
|
||||||
|
|------------------|------------------------------------------------------------|
|
||||||
|
| Ближайшие 2-3 нед. | Первый студент может подойти к курсовой |
|
||||||
|
| До этого момента | Подготовить: ТЗ, стартовое состояние main, ветку solution |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Открытые вопросы (TODO)
|
||||||
|
|
||||||
|
1. ~~**Выбор эталонного среза**~~ — **РЕШЕНО.** Эталон: `sales_report` и её
|
||||||
|
цепочка. Задание: остальные 4 витрины + 3 STG/ODS + 3 DDS-измерения.
|
||||||
|
SCD2 (`dim_routes`) — задание с подсказками в ТЗ. (Раздел 6)
|
||||||
|
|
||||||
|
2. ~~**Формат DQ для самоконтроля**~~ — **РЕШЕНО.** Отдельный валидационный DAG
|
||||||
|
(`bookings_validate.py`). Под капотом — SQL-проверки. Студент запускает DAG
|
||||||
|
в Airflow UI и видит красные/зелёные таски по слоям. Дополнительный бонус —
|
||||||
|
практика чтения логов Airflow. (см. Раздел 6.1 ниже)
|
||||||
|
|
||||||
|
3. **Вынос CSV-пайплайна** — перенести `csv_to_greenplum.py`,
|
||||||
|
`csv_to_greenplum_dq.py`, `ddl_greenplum_base.py`, `helpers/greenplum.py`,
|
||||||
|
`sql/base/orders_ddl.sql` и связанные тесты в репозиторий
|
||||||
|
[airflow-manual](https://github.com/dementev-dev/airflow-manual).
|
||||||
|
Решение принято, нужно выполнить.
|
||||||
|
|
||||||
|
4. **Подготовка main** — какие изменения внести в main для стартового
|
||||||
|
состояния (убрать лишние реализации, добавить TODO-маркеры)?
|
||||||
|
|
||||||
|
5. **Название** — рабочее: «Greenplum Bookings DWH». Финализировать.
|
||||||
Reference in New Issue
Block a user