Files
airflow-greenplum/docs/design/db_schema.md
T
ddadminandClaude Opus 4.6 5b98a5b203 docs(main): очистка docs, адаптация для студентов, починка ссылок
Шаг 5 плана main/solution split:
- Удалены внутренние документы с main: plans/, archive/, PRD,
  assignment_design, pxf_bookings, bookings_tz, benchmarks, TODO.md
- AGENTS.md: убраны упоминания plans/archive, agent-dag-testing
- Починены 19 битых markdown-ссылок во всех оставшихся файлах
- bookings_ods_design: airplanes/seats помечены как студенческие,
  обновлён DAG-граф (routes без зависимости от airplanes)
- bookings_dds_design: обновлено описание DQ факта (student SK)
- bookings_to_gp_dds: обновлена DQ-семантика для main
- qa-plan: уточнено — ods.airplanes/seats пусты by design на main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:14:22 +03:00

315 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Схема БД DWH (Bookings → Greenplum)
> **Статус:** Все слои реализованы в ветке `solution` (STG, ODS, DDS, DM).
> На ветке `main` студенческие таблицы ODS (`airplanes`, `seats`), DDS-измерения
> (`dim_airplanes`, `dim_passengers`, `dim_routes`) и студенческие DM-витрины
> (`airport_traffic`, `route_performance`, `monthly_overview`, `passenger_loyalty`)
> — заглушки (`SELECT 1;`). Данные появятся после реализации заданий.
Архитектура хранилища данных (DWH) для учебного проекта Airflow + Greenplum.
Источник — демо-БД `bookings` (Postgres). Документ даёт цельный взгляд «сверху»;
детали реализации — в дизайн-документах слоёв.
---
## Полная схема потоков данных (Data Lineage)
```mermaid
graph LR
%% Стили
classDef source fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef stg fill:#fff9c4,stroke:#fbc02d,stroke-width:2px;
classDef ods fill:#e0f2f1,stroke:#00695c,stroke-width:2px;
classDef dim fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
classDef fact fill:#ffccbc,stroke:#bf360c,stroke-width:4px;
classDef dm fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
%% 1. Source
subgraph Source_Postgres [Source: Postgres Bookings]
direction TB
SRC_Airports[airports_data]:::source
SRC_Airplanes[airplanes_data]:::source
SRC_Routes[routes]:::source
SRC_Seats[seats]:::source
SRC_Bookings[bookings]:::source
SRC_Tickets[tickets]:::source
SRC_Flights[flights]:::source
SRC_Segments[segments]:::source
SRC_Boarding[boarding_passes]:::source
end
%% 2. STAGING (Load 1-to-1, AO-Row)
subgraph STG_Layer [Layer: STG Staging]
direction TB
STG_Airports[stg.airports]:::stg
STG_Airplanes[stg.airplanes]:::stg
STG_Routes[stg.routes]:::stg
STG_Seats[stg.seats]:::stg
STG_Bookings[stg.bookings]:::stg
STG_Tickets[stg.tickets]:::stg
STG_Flights[stg.flights]:::stg
STG_Segments[stg.segments]:::stg
STG_Boarding[stg.boarding_passes]:::stg
end
%% Links Source to STG
SRC_Airports --> STG_Airports
SRC_Airplanes --> STG_Airplanes
SRC_Routes --> STG_Routes
SRC_Seats --> STG_Seats
SRC_Bookings --> STG_Bookings
SRC_Tickets --> STG_Tickets
SRC_Flights --> STG_Flights
SRC_Segments --> STG_Segments
SRC_Boarding --> STG_Boarding
%% 3. ODS (3NF, Clean, Type)
subgraph ODS_Layer [Layer: ODS Operational Core]
direction TB
ODS_Airports[ods.airports]:::ods
ODS_Airplanes[ods.airplanes]:::ods
ODS_Routes[ods.routes]:::ods
ODS_Seats[ods.seats]:::ods
ODS_Bookings[ods.bookings]:::ods
ODS_Tickets[ods.tickets]:::ods
ODS_Flights[ods.flights]:::ods
ODS_Segments[ods.segments]:::ods
ODS_Boarding[ods.boarding_passes]:::ods
end
%% Links STG to ODS
STG_Airports --> ODS_Airports
STG_Airplanes --> ODS_Airplanes
STG_Routes --> ODS_Routes
STG_Seats --> ODS_Seats
STG_Bookings --> ODS_Bookings
STG_Tickets --> ODS_Tickets
STG_Flights --> ODS_Flights
STG_Segments --> ODS_Segments
STG_Boarding --> ODS_Boarding
%% 4. DDS (Star Schema)
subgraph DDS_Layer [Layer: DDS Star Schema]
direction TB
DIM_Calendar[dds.dim_calendar]:::dim
DIM_Airports[dds.dim_airports]:::dim
DIM_Airplanes[dds.dim_airplanes]:::dim
DIM_Tariffs[dds.dim_tariffs]:::dim
DIM_Passengers[dds.dim_passengers]:::dim
DIM_Routes[dds.dim_routes SCD2]:::dim
FACT_Sales[dds.fact_flight_sales]:::fact
end
%% ODS to DDS Dimensions
ODS_Airports --> DIM_Airports
ODS_Airplanes --> DIM_Airplanes
ODS_Seats -.->|total_seats| DIM_Airplanes
ODS_Segments -.->|DISTINCT| DIM_Tariffs
ODS_Tickets -->|Unique passengers| DIM_Passengers
ODS_Routes -->|SCD2 hashdiff| DIM_Routes
DIM_Airports -.->|cities| DIM_Routes
DIM_Airplanes -.->|model, seats| DIM_Routes
%% ODS to Fact
ODS_Segments -->|Main stream| FACT_Sales
ODS_Tickets -->|book_ref, passenger_id| FACT_Sales
ODS_Bookings -->|book_date| FACT_Sales
ODS_Flights -->|schedule, route_no| FACT_Sales
ODS_Routes -->|dep/arr airports| FACT_Sales
ODS_Boarding -->|LEFT JOIN seat_no| FACT_Sales
%% Dimensions to Fact
DIM_Calendar -->|calendar_sk| FACT_Sales
DIM_Airports -->|dep/arr _sk| FACT_Sales
DIM_Airplanes -->|airplane_sk| FACT_Sales
DIM_Tariffs -->|tariff_sk| FACT_Sales
DIM_Passengers -->|passenger_sk| FACT_Sales
DIM_Routes -->|route_sk| FACT_Sales
%% 5. DM (Vitrines)
subgraph DM_Layer [Layer: DM Data Marts]
direction TB
DM_Sales[dm.sales_report]:::dm
DM_Traffic[dm.airport_traffic]:::dm
DM_Route[dm.route_performance]:::dm
DM_Monthly[dm.monthly_overview]:::dm
DM_Loyalty[dm.passenger_loyalty]:::dm
end
%% DDS to DM
FACT_Sales --> DM_Sales
FACT_Sales --> DM_Traffic
FACT_Sales --> DM_Route
FACT_Sales --> DM_Monthly
FACT_Sales --> DM_Loyalty
DIM_Airports -.-> DM_Sales
DIM_Airports -.-> DM_Traffic
DIM_Tariffs -.-> DM_Sales
DIM_Tariffs -.-> DM_Loyalty
DIM_Calendar -.-> DM_Sales
DIM_Calendar -.-> DM_Traffic
DIM_Calendar -.-> DM_Route
DIM_Calendar -.-> DM_Monthly
DIM_Calendar -.-> DM_Loyalty
DIM_Routes -.-> DM_Route
DIM_Routes -.-> DM_Monthly
DIM_Routes -.-> DM_Loyalty
DIM_Airplanes -.-> DM_Monthly
DIM_Passengers -.-> DM_Loyalty
```
---
## Сводка объектов по слоям
### STG (Staging)
Сырые данные из источника, все бизнес-поля как TEXT. Хранение: AO Row (zstd).
Служебные поля: `event_ts`, `_load_ts`, `_load_id`.
| Таблица | Источник (PXF) | Ключ | Стратегия загрузки | Distribution |
|---------|---------------|------|-------------------|--------------|
| `stg.bookings` | `bookings.bookings` | `book_ref` | Инкремент (book_date) | `book_ref` |
| `stg.tickets` | `bookings.tickets` | `ticket_no` | Инкремент (через bookings) | `book_ref` |
| `stg.flights` | `bookings.flights` | `flight_id` | Инкремент (scheduled_departure) | `flight_id` |
| `stg.segments` | `bookings.segments` | `(ticket_no, flight_id)` | Инкремент (через tickets) | `ticket_no` |
| `stg.boarding_passes` | `bookings.boarding_passes` | `(ticket_no, flight_id)` | Инкремент (через tickets/bookings) | `ticket_no` |
| `stg.airports` | `bookings.airports_data` | `airport_code` | Full snapshot | `airport_code` |
| `stg.airplanes` | `bookings.airplanes_data` | `airplane_code` | Full snapshot | `airplane_code` |
| `stg.routes` | `bookings.routes` | `(route_no, validity)` | Full snapshot | `route_no` |
| `stg.seats` | `bookings.seats` | `(airplane_code, seat_no)` | Full snapshot | `airplane_code` |
Детали: [`bookings_stg_design.md`](bookings_stg_design.md)
### ODS (Operational Data Store)
Очищенные данные с корректными типами. Справочники: AO Row (TRUNCATE+INSERT).
Транзакции: Heap (SCD1 UPSERT). Служебные поля: `_load_id`, `_load_ts`,
`event_ts` (для транзакционных таблиц).
| Таблица | Стратегия | Storage | Ключевые преобразования |
|---------|-----------|---------|------------------------|
| `ods.bookings` | SCD1 UPSERT (HWM) | Heap | `total_amount` TEXT → NUMERIC |
| `ods.tickets` | SCD1 UPSERT (HWM) | Heap | `outbound` TEXT → BOOLEAN |
| `ods.flights` | SCD1 UPSERT (HWM) | Heap | TEXT → INTEGER, TIMESTAMP WITH TIME ZONE |
| `ods.segments` | SCD1 UPSERT (HWM) | Heap | `price` TEXT → `amount` NUMERIC |
| `ods.boarding_passes` | SCD1 UPSERT (HWM) | Heap | `boarding_no` TEXT → INTEGER |
| `ods.airports` | TRUNCATE+INSERT | AO Row | JSON → отдельные поля (`airport_name`, `city`) |
| `ods.airplanes` | TRUNCATE+INSERT | AO Row | `range`/`speed` TEXT → INTEGER |
| `ods.routes` | TRUNCATE+INSERT | AO Row | `days_of_week` → INTEGER[], `scheduled_time` → TIME |
| `ods.seats` | TRUNCATE+INSERT | AO Row | Без преобразований |
Детали: [`bookings_ods_design.md`](bookings_ods_design.md)
### DDS (Detailed Data Store — Star Schema)
Измерения с суррогатными ключами. Факт в центре звезды.
SK генерация: `MAX(sk) + ROW_NUMBER()` (безопасно при `concurrency=1`).
#### Измерения
| Измерение | BK | SK | SCD | Storage | Источник |
|-----------|----|----|-----|---------|----------|
| `dim_calendar` | `date_actual` | `calendar_sk` | Static | AO Row | Генерация (20162030) |
| `dim_airports` | `airport_bk` | `airport_sk` | SCD1 | Heap | `ods.airports` |
| `dim_airplanes` | `airplane_bk` | `airplane_sk` | SCD1 | Heap | `ods.airplanes` + `ods.seats` (total_seats) |
| `dim_tariffs` | `fare_conditions` | `tariff_sk` | SCD1 | AO Row | `ods.segments` (DISTINCT) |
| `dim_passengers` | `passenger_id` | `passenger_sk` | SCD1 | Heap | `ods.tickets` (дедупликация) |
| `dim_routes` | `route_bk` | `route_sk` | **SCD2** | Heap | `ods.routes` + `dim_airports` + `dim_airplanes` |
#### Факт
`dds.fact_flight_sales` — зерно: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`).
| FK | Lookup-путь | Примечание |
|----|-------------|------------|
| `calendar_sk` | `dim_calendar` | Дата вылета |
| `departure_airport_sk` | `ods.routes``dim_airports` | Аэропорт вылета (эталонный справочник) |
| `arrival_airport_sk` | `ods.routes``dim_airports` | Аэропорт прилёта (эталонный справочник) |
| `airplane_sk` | `dim_routes``dim_airplanes` | Самолёт (point-in-time SCD2) |
| `tariff_sk` | `dim_tariffs` | Тариф |
| `passenger_sk` | `dim_passengers` | Пассажир |
| `route_sk` | `dim_routes` | Версия маршрута (SCD2, point-in-time) |
Метрики: `price` (NUMERIC), `is_boarded` (BOOLEAN).
Degenerate keys: `book_ref`, `ticket_no`, `flight_id`, `book_date`, `seat_no`.
Детали: [`bookings_dds_design.md`](bookings_dds_design.md)
### DM (Data Marts — Витрины)
Аналитические витрины поверх DDS. Каждая отвечает на конкретный бизнес-вопрос.
| Витрина | Бизнес-вопрос | Зерно | Стратегия | Storage |
|---------|--------------|-------|-----------|---------|
| `dm.sales_report` | Выручка и boarding rate по направлениям/тарифам/дням | (flight_date, dep_sk, arr_sk, tariff_sk) | UPSERT (HWM) | Heap |
| `dm.airport_traffic` | Пассажиропоток аэропортов по дням | (traffic_date, airport_sk) | UPSERT (HWM) | Heap |
| `dm.route_performance` | Эффективность маршрутов за всё время | route_bk | Full Rebuild | AO Column (zstd) |
| `dm.monthly_overview` | Помесячная динамика по типам самолётов | (year, month, airplane_sk) | UPSERT (HWM) | Heap |
| `dm.passenger_loyalty` | Профиль лояльности пассажиров | passenger_sk | UPSERT (затронутые ключи) | Heap |
Детали: [`bookings_dm_design.md`](bookings_dm_design.md)
---
## Ключевые договорённости
- **Нейминг полей**: [`naming_conventions.md`](naming_conventions.md)
- **DQ-проверки**: SQL-скрипты с `RAISE EXCEPTION` (не отдельный DQ-слой).
На ветке `main` студенческие DQ-скрипты (`airplanes_dq.sql`, `seats_dq.sql`,
student dims/DM) — заглушки (`SELECT 1;`) без проверок.
- **Инкремент STG**: для `tickets` опорная дата — из `bookings.book_date`
- **Point-in-time JOIN**: факт ↔ `dim_routes` по `[valid_from, valid_to)`
- **Суррогатные ключи**: `MAX(sk) + ROW_NUMBER()` (не SERIAL — GP-специфика)
---
## Обучающие материалы
### Глоссарий
| Термин | Объяснение |
|--------|-----------|
| **Зерно факта (Fact Grain)** | Минимальная единица в факте. Здесь — один сегмент билета. |
| **Суррогатный ключ (SK)** | Технический INT-ключ, генерируемый в DWH. |
| **Бизнес-ключ (BK)** | Ключ из источника (`airport_code`, `passenger_id`). |
| **Star Schema** | Факт в центре, измерения вокруг (без snowflake-подтаблиц). |
| **SCD Type 1** | Перезапись атрибутов без истории. |
| **SCD Type 2** | Версионирование: `valid_from`/`valid_to`, `hashdiff`. |
| **AO Row/Column** | Append-Only хранение (Row или Column). Не поддерживает UPDATE. |
| **Heap** | Стандартное хранение с поддержкой UPDATE/DELETE. |
| **HWM (High Water Mark)** | Отсечка по `MAX(_load_ts)` для инкрементальной загрузки. |
### На что обратить внимание
**Обогащение измерений:**
`seats` + `airplanes``dim_airplanes` — пример обогащения (total_seats).
**«Майнинг» измерений из транзакций:**
`tickets``dim_passengers` — в источнике нет таблицы «Пассажиры». Извлекаем
уникальных пассажиров из билетов с дедупликацией.
**Нормализация:**
`segments.fare_conditions``dim_tariffs` — выносим строковый атрибут в отдельный
справочник для компактного INT-ключа в факте.
**Почему нет `dim_bookings`:**
`bookings` — транзакция, не справочник. `book_ref` и `book_date` хранятся
как degenerate keys в факте.
**Point-in-time JOIN (SCD2):**
Факт присоединяется к той версии маршрута, которая действовала на дату вылета:
`scheduled_departure::DATE >= valid_from AND (valid_to IS NULL OR ... < valid_to)`.
---
## Связанные документы
- [`bookings_stg_design.md`](bookings_stg_design.md) — дизайн STG
- [`bookings_ods_design.md`](bookings_ods_design.md) — дизайн ODS (SCD1, batch contract, DQ)
- [`bookings_dds_design.md`](bookings_dds_design.md) — дизайн DDS (Star Schema, SCD2)
- [`bookings_dm_design.md`](bookings_dm_design.md) — дизайн DM (5 витрин)
- [`naming_conventions.md`](naming_conventions.md) — нейминг полей
- Часовые пояса и настройка PXF — в ветке `solution` (`docs/reference/`)
- [`../assignment/analyst_spec.md`](../assignment/analyst_spec.md) — ТЗ от аналитика (курсовое задание)