diff --git a/docs/internal/PRD.md b/docs/internal/PRD.md new file mode 100644 index 0000000..e93825c --- /dev/null +++ b/docs/internal/PRD.md @@ -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-проверки** — готовые проверки, + которые студент запускает для самоконтроля. + +### Что делает студент + +- Пишет 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». Финализировать.