Доработки GLM

This commit is contained in:
2026-02-28 22:12:22 +03:00
parent 52031c75e6
commit 1812ced796
+113 -51
View File
@@ -6,13 +6,21 @@
Эта документация описывает архитектуру хранилища данных (DWH) для учебного проекта Airflow + Greenplum. Источник данных — демо-БД `bookings` (Postgres). Эта документация описывает архитектуру хранилища данных (DWH) для учебного проекта Airflow + Greenplum. Источник данных — демо-БД `bookings` (Postgres).
### Ключевые договорённости (для LLM и студентов) **Целевые аудитории:**
- **LLM/Разработчики**: Технические спецификации для реализации (см. раздел "Спецификации для реализации")
- **Студенты**: Обучающие материалы и пояснения (см. раздел "Обучающие материалы")
- **Источник**: используем основные таблицы схемы `bookings` (табличные данные, не `VIEW`). ---
- **Зерно факта `fact.flight_sales`**: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`, источник: `segments`).
- **Обязательная связь для аэропортов и самолёта**: `flights.route_no → routes → (departure_airport, arrival_airport, airplane_code)`. ## Спецификации для реализации (для LLM и разработчиков)
- **Даты**: как минимум различаем `book_date` (дата покупки) и `scheduled_departure` (дата/время вылета).
- **Инкремент в STG**: для `tickets` опорная дата берётся из `bookings.book_date`, потому что в `tickets` нет собственного поля времени изменения. ### Ключевые договорённости
- **Источник**: используем основные таблицы схемы `bookings` (табличные данные, не `VIEW`)
- **Зерно факта `fact.flight_sales`**: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`, источник: `segments`)
- **Обязательная связь для аэропортов и самолёта**: `flights.route_no → routes → (departure_airport, arrival_airport, airplane_code)`
- **Даты**: как минимум различаем `book_date` (дата покупки) и `scheduled_departure` (дата/время вылета)
- **Инкремент в STG**: для `tickets` опорная дата берётся из `bookings.book_date`, потому что в `tickets` нет собственного поля времени изменения
### Статус реализации по слоям ### Статус реализации по слоям
@@ -20,10 +28,64 @@
|------|--------|-------------| |------|--------|-------------|
| **Source** | ✅ Готово | Демо-БД bookings (Postgres) | | **Source** | ✅ Готово | Демо-БД bookings (Postgres) |
| **STG** | ⚠️ В процессе | 2 из 9 таблиц (bookings, tickets) | | **STG** | ⚠️ В процессе | 2 из 9 таблиц (bookings, tickets) |
| **DQ** | ⚠️ В процессе | Есть скрипты для bookings и tickets |
| **ODS** | ❌ Не реализован | Планируется | | **ODS** | ❌ Не реализован | Планируется |
| **DDS** | ❌ Не реализован | Планируется | | **DDS** | ❌ Не реализован | Планируется |
### Архитектура слоёв
#### STG (Staging Layer)
- **Назначение**: Сырой слой, максимально близкий к источнику, без бизнес-логики
- **Хранение**: AO-Row (Append-Only Row-oriented) для эффективной загрузки больших объёмов
- **Типы данных**: Бизнес-колонки как `TEXT`, тех.колонки как `TIMESTAMP`
- **Инкрементальная загрузка**: Опорное поле `src_created_at_ts` (из `book_date` для tickets)
- **Технологические колонки**:
- `src_created_at_ts TIMESTAMP` — дата/время из источника для инкремента
- `load_dttm TIMESTAMP NOT NULL DEFAULT now()` — когда запись была загружена
- `batch_id TEXT NOT NULL` — идентификатор пачки (например, `{{ ds_nodash }}`)
#### ODS (Operational Data Store)
- **Назначение**: Очищенные данные в 3NF, готовые для аналитики
- **Хранение**: Heap для частых чтений и обновлений
- **Трансформации**: Очистка, приведение типов, нормализация
- **Связи**: Все связи через бизнес-ключи (без суррогатных ключей)
#### DDS (Data Delivery System)
- **Назначение**: Star Schema для аналитики и отчётности
- **Хранение**: Heap или AO-CO (Append-Only Column-oriented) для аналитических запросов
- **Структура**: Измерения (Dimensions) + Факты (Facts)
- **Ключи**: Суррогатные ключи (SK) для измерений, FK в фактах
### Измерения DDS (Dimensions)
| Измерение | Бизнес-ключ | Суррогатный ключ | Атрибуты |
|-----------|-------------|------------------|----------|
| `dim.calendar` | `date DATE` | `calendar_sk INT` | `year`, `month`, `day`, `day_of_week`, `is_holiday` |
| `dim.airports` | `airport_code CHAR(3)` | `airport_sk INT` | `airport_name`, `city`, `timezone`, `coordinates` |
| `dim.airplanes` | `airplane_code TEXT` | `airplane_sk INT` | `model`, `total_seats`, `range_km` |
| `dim.tariffs` | `fare_conditions TEXT` | `tariff_sk INT` | `fare_conditions` (Economy/Comfort/Business) |
| `dim.passengers` | `passenger_id TEXT` | `passenger_sk INT` | `passenger_name` (SCD Type 1) |
### Факт DDS (Fact)
`fact.flight_sales`:
- **Зерно**: 1 строка = 1 сегмент билета (`ticket_no` + `flight_id`)
- **FK на измерения**:
- `calendar_sk` — ссылка на дату вылета
- `departure_airport_sk` — аэропорт вылета
- `arrival_airport_sk` — аэропорт прилёта
- `airplane_sk` — самолёт
- `tariff_sk` — тариф
- `passenger_sk` — пассажир
- **Метрики**:
- `price NUMERIC` — стоимость сегмента
- `is_boarded BOOLEAN` — сел ли пассажир в самолёт (из boarding_passes)
- **Атрибуты**:
- `book_ref TEXT` — бизнес-ключ бронирования
- `book_date DATE` — дата покупки
- `ticket_no TEXT` — номер билета
- `flight_id INT` — ID рейса
- `seat_no TEXT` — место (если есть)
--- ---
## Полная схема потоков данных (Data Lineage) ## Полная схема потоков данных (Data Lineage)
@@ -33,7 +95,6 @@ graph LR
%% Стили %% Стили
classDef source fill:#e1f5fe,stroke:#01579b,stroke-width:2px; classDef source fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef stg fill:#fff9c4,stroke:#fbc02d,stroke-width:2px; classDef stg fill:#fff9c4,stroke:#fbc02d,stroke-width:2px;
classDef dq fill:#ffe0b2,stroke:#ef6c00,stroke-width:2px;
classDef ods fill:#e0f2f1,stroke:#00695c,stroke-width:2px; classDef ods fill:#e0f2f1,stroke:#00695c,stroke-width:2px;
classDef dim fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px; classDef dim fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
classDef fact fill:#ffccbc,stroke:#bf360c,stroke-width:4px; classDef fact fill:#ffccbc,stroke:#bf360c,stroke-width:4px;
@@ -77,24 +138,7 @@ graph LR
SRC_Segments --> STG_Segments SRC_Segments --> STG_Segments
SRC_Boarding --> STG_Boarding SRC_Boarding --> STG_Boarding
%% 3. DATA QUALITY (Quality Checks) %% 3. ODS (3NF, Clean, Type, Heap)
subgraph DQ_Layer [Layer: DQ Data Quality]
direction TB
DQ_Bookings[dq.bookings_checks]:::dq
DQ_Tickets[dq.tickets_checks]:::dq
DQ_Routes[dq.routes_checks]:::dq
DQ_Flights[dq.flights_checks]:::dq
DQ_Segments[dq.segments_checks]:::dq
end
%% Links STG to DQ
STG_Bookings --> DQ_Bookings
STG_Tickets --> DQ_Tickets
STG_Routes --> DQ_Routes
STG_Flights --> DQ_Flights
STG_Segments --> DQ_Segments
%% 4. ODS (3NF, Clean, Type, Heap)
subgraph ODS_Layer [Layer: ODS Operational Core] subgraph ODS_Layer [Layer: ODS Operational Core]
direction TB direction TB
ODS_Airports[ods.airports]:::ods ODS_Airports[ods.airports]:::ods
@@ -108,18 +152,18 @@ graph LR
ODS_Boarding[ods.boarding_passes]:::ods ODS_Boarding[ods.boarding_passes]:::ods
end end
%% Links DQ to ODS %% Links STG to ODS
DQ_Bookings --> ODS_Bookings
DQ_Tickets --> ODS_Tickets
DQ_Routes --> ODS_Routes
DQ_Flights --> ODS_Flights
DQ_Segments --> ODS_Segments
STG_Airports --> ODS_Airports STG_Airports --> ODS_Airports
STG_Airplanes --> ODS_Airplanes STG_Airplanes --> ODS_Airplanes
STG_Routes --> ODS_Routes
STG_Seats --> ODS_Seats STG_Seats --> ODS_Seats
STG_Bookings --> ODS_Bookings
STG_Tickets --> ODS_Tickets
STG_Flights --> ODS_Flights
STG_Segments --> ODS_Segments
STG_Boarding --> ODS_Boarding STG_Boarding --> ODS_Boarding
%% 5. DDS (Star Schema) %% 4. DDS (Star Schema)
subgraph DDS_Layer [Layer: DDS Star Schema] subgraph DDS_Layer [Layer: DDS Star Schema]
direction TB direction TB
@@ -165,24 +209,41 @@ graph LR
--- ---
## Пояснения к схеме (для студентов) ## Обучающие материалы (для студентов)
### Глоссарий ключевых терминов
| Термин | Объяснение |
|--------|-----------|
| **Зерно факта (Fact Grain)** | Минимальная единица измерения в факте. Для `fact.flight_sales` — это один сегмент билета. |
| **Суррогатный ключ (Surrogate Key, SK)** | Технический ключ (обычно INT), который генерируется в DWH и не зависит от бизнес-ключа. |
| **Бизнес-ключ (Business Key)** | Ключ из источника (например, `airport_code`, `passenger_id`). |
| **Star Schema** | Модель данных, где факт в центре, а измерения вокруг него (как звезда). |
| **SCD Type 1** | Slowly Changing Dimension Type 1: при изменении данных просто перезаписываем старую запись. |
| **SCD Type 2** | Slowly Changing Dimension Type 2: при изменении данных создаём новую запись с датой начала/действия. |
| **AO-Row** | Append-Only Row-oriented: хранение данных по строкам, только добавление (без UPDATE/DELETE). |
| **Heap** | Обычное хранение данных (как в обычной таблице), поддерживает UPDATE/DELETE. |
| **Инкрементальная загрузка** | Загрузка только новых/изменённых данных за период, а не всей таблицы. |
### Пояснения к схеме
Эта диаграмма покрывает основные таблицы источника и показывает логику их трансформации. Вот на что стоит обратить внимание при обучении: Эта диаграмма покрывает основные таблицы источника и показывает логику их трансформации. Вот на что стоит обратить внимание при обучении:
### 1. Ветка справочников (Reference Data) #### 1. Ветка справочников (Reference Data)
* **`seats` + `airplanes``dim.airplanes`**: Здесь мы показываем пример **обогащения**. Таблица `seats` сама по себе в аналитике редко нужна отдельной сущностью. Мы используем её в ODS, чтобы посчитать общее количество мест (`total_seats`) и добавить это как атрибут в измерение самолётов (`dim.airplanes`). **`seats` + `airplanes``dim.airplanes`**: Здесь мы показываем пример **обогащения**. Таблица `seats` сама по себе в аналитике редко нужна отдельной сущностью. Мы используем её в ODS, чтобы посчитать общее количество мест (`total_seats`) и добавить это как атрибут в измерение самолётов (`dim.airplanes`).
* **`airports``dim.airports`**: Простой перенос (1-в-1), но в DDS мы можем добавить, например, поле `city_ru` и `city_en` как отдельные колонки, убрав JSON, который есть в источнике. **`airports``dim.airports`**: Простой перенос (1-в-1), но в DDS мы можем добавить, например, поле `city_ru` и `city_en` как отдельные колонки, убрав JSON, который есть в источнике.
### 2. Ветка генерации измерений (Dimension Generation) #### 2. Ветка генерации измерений (Dimension Generation)
* **`tickets``dim.passengers`**: Это самая сложная трансформация для измерения. В источнике нет таблицы "Пассажиры". Мы должны объяснить студентам, что мы "майним" пассажиров из билетов. Важно: один и тот же пассажир может иметь разные записи с разными именами (опечатки, изменение фамилии), поэтому в проде часто делают логику SCD Type 2 для отслеживания изменений. **`tickets``dim.passengers`**: Это самая сложная трансформация для измерения. В источнике нет таблицы "Пассажиры". Мы должны объяснить студентам, что мы "майним" пассажиров из билетов. Важно: один и тот же пассажир может иметь разные записи с разными именами (опечатки, изменение фамилии), поэтому в проде часто делают логику SCD Type 2 для отслеживания изменений.
- Для домашки (и первого эталонного решения) обычно достаточно **SCD Type 1**: одна актуальная запись на `passenger_id`, а SCD2 можно оставить как усложнение.
* **`segments``dim.tariffs`**: Таблицы тарифов физически нет в источнике, она хранится строкой (`fare_conditions`: Economy/Comfort/Business) в таблице `segments`. Мы выносим её в отдельный справочник (нормализация), чтобы в факте хранить маленький `INT` ключ, а не длинную строку. - Для домашки (и первого эталонного решения) обычно достаточно **SCD Type 1**: одна актуальная запись на `passenger_id`, а SCD2 можно оставить как усложнение.
### 3. Сборка Факта (`fact.flight_sales`) **`segments``dim.tariffs`**: Таблицы тарифов физически нет в источнике, она хранится строкой (`fare_conditions`: Economy/Comfort/Business) в таблице `segments`. Мы выносим её в отдельный справочник (нормализация), чтобы в факте хранить маленький `INT` ключ, а не длинную строку.
#### 3. Сборка Факта (`fact.flight_sales`)
Это центр звезды. Мы собираем его из шести ODS таблиц: Это центр звезды. Мы собираем его из шести ODS таблиц:
@@ -193,9 +254,9 @@ graph LR
5. **`ods.routes`**: Приджойниваем по `route_no`, чтобы получить аэропорты вылета/прилёта и `airplane_code``flights` этих полей нет напрямую). 5. **`ods.routes`**: Приджойниваем по `route_no`, чтобы получить аэропорты вылета/прилёта и `airplane_code``flights` этих полей нет напрямую).
6. **`ods.boarding_passes`**: Приджойниваем (LEFT JOIN), чтобы узнать, **сел ли пассажир реально в самолёт** и на какое место (`seat_no`). Это важный бизнес-аспект: билет куплен, но посадочный не выдан = пассажир не летел. 6. **`ods.boarding_passes`**: Приджойниваем (LEFT JOIN), чтобы узнать, **сел ли пассажир реально в самолёт** и на какое место (`seat_no`). Это важный бизнес-аспект: билет куплен, но посадочный не выдан = пассажир не летел.
### 4. Почему нет `dim.bookings`? #### 4. Почему нет `dim.bookings`?
В классической Star Schema измерения — это справочники (airports, airplanes, passengers), а факты — транзакции/события (sales, bookings). В классической Star Schema измерения — это справочники (airports, airplanes, passengers), а факты — транзакции/события (sales, bookings).
`bookings` — это транзакционная таблица, а не справочник. Вместо отдельного измерения `dim.bookings` мы храним: `bookings` — это транзакционная таблица, а не справочник. Вместо отдельного измерения `dim.bookings` мы храним:
- `book_ref` — бизнес-ключ бронирования (в факте) - `book_ref` — бизнес-ключ бронирования (в факте)
@@ -203,7 +264,7 @@ graph LR
Это позволяет отвечать на вопросы типа: *"За сколько дней до вылета люди обычно покупают билеты?"* (разница между `book_date` и датой вылета из `dim.calendar`). Это позволяет отвечать на вопросы типа: *"За сколько дней до вылета люди обычно покупают билеты?"* (разница между `book_date` и датой вылета из `dim.calendar`).
### 5. Суррогатные ключи (Surrogate Keys) #### 5. Суррогатные ключи (Surrogate Keys)
В Star Schema факт должен ссылаться на суррогатные ключи (SK) измерений, а не на бизнес-ключи: В Star Schema факт должен ссылаться на суррогатные ключи (SK) измерений, а не на бизнес-ключи:
@@ -213,13 +274,14 @@ graph LR
| `airplane_code TEXT` | `airplane_sk INT` | Меньший размер, стабильность | | `airplane_code TEXT` | `airplane_sk INT` | Меньший размер, стабильность |
| `passenger_id TEXT` | `passenger_sk INT` | Меньший размер, отслеживание изменений | | `passenger_id TEXT` | `passenger_sk INT` | Меньший размер, отслеживание изменений |
### 6. Слой DQ (Data Quality) ---
Между STG и ODS добавлен слой Data Quality для проверки качества данных. В проекте уже есть скрипты: ## Связанные документы
- `sql/stg/bookings_dq.sql`
- `sql/stg/tickets_dq.sql`
На схеме показаны примеры проверок для всех таблиц, которые должны быть реализованы. - [`docs/internal/bookings_stg_design.md`](docs/internal/bookings_stg_design.md) — Детальный дизайн STG слоя для bookings
- [`docs/internal/bookings_tz.md`](docs/internal/bookings_tz.md) — Работа с часовыми поясами в источнике
- [`docs/internal/pxf_bookings.md`](docs/internal/pxf_bookings.md) — Настройка PXF для чтения из bookings-db
- [`TESTING.md`](TESTING.md) — Пошаговый чек-лист для тестирования стенда
--- ---
@@ -227,6 +289,7 @@ graph LR
| Дата | Версия | Описание изменений | | Дата | Версия | Описание изменений |
|------|--------|-------------------| |------|--------|-------------------|
| 2025-01-17 | 2.0 | Удалён слой DQ для упрощения учебного стенда. Добавлены спецификации для LLM и обучающие материалы для студентов. Добавлен глоссарий терминов. |
| 2025-01-17 | 1.1 | Исправлены названия таблиц (`aircrafts_data``airplanes_data`, `ticket_flights``segments`), удалено `dim.bookings`, добавлены суррогатные ключи, добавлен слой DQ, исправлены связи | | 2025-01-17 | 1.1 | Исправлены названия таблиц (`aircrafts_data``airplanes_data`, `ticket_flights``segments`), удалено `dim.bookings`, добавлены суррогатные ключи, добавлен слой DQ, исправлены связи |
| 2025-01-XX | 1.0 | Первоначальная версия | | 2025-01-XX | 1.0 | Первоначальная версия |
@@ -235,7 +298,6 @@ graph LR
## TODO ## TODO
- [ ] Реализовать STG слой полностью (все 9 таблиц) - [ ] Реализовать STG слой полностью (все 9 таблиц)
- [ ] Реализовать DQ слой для всех таблиц
- [ ] Реализовать ODS слой - [ ] Реализовать ODS слой
- [ ] Реализовать DDS слой (измерения и факт) - [ ] Реализовать DDS слой (измерения и факт)
- [ ] Создать DAG для загрузки ODS - [ ] Создать DAG для загрузки ODS