Files
airflow-greenplum/docs/design/db_schema.md
T

218 lines
17 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.
# Как устроено хранилище авиаперевозок
Разберем одну поездку: как бронирование связано с билетами и перелетами,
какие строки попадут в факт и как сложится выручка по направлениям.
Найти эти строки в структуре хранилища поможет [интерактивная карта](architecture-map.html).
Карта хранит подробности объектов и связей, а это руководство задает маршрут чтения.
## Как открыть карту
Из локальной копии репозитория откройте `docs/design/architecture-map.html`
в браузере: через файловый менеджер или командой из корня проекта:
```bash
xdg-open docs/design/architecture-map.html
```
На Windows можно открыть этот же файл в Проводнике. Сервер, интернет и работающий
Docker для карты не нужны: все ее данные включены в HTML.
Ссылки на SQL, DAG, задание и это руководство открывают файлы в Gitea:
документы — с форматированием, код — с подсветкой синтаксиса.
Для этих переходов нужен интернет. Файлы берутся из текущей ветки репозитория
по умолчанию.
**В Gitea ссылка на HTML обычно показывает исходный текст, а не карту.**
Сначала откройте локальный HTML. Адресная ссылка вида
`architecture-map.html#node=dds.fact_flight_sales` сразу выбирает таблицу;
ее можно открыть из локального просмотра этого руководства или добавить
часть после `#` к адресу уже открытой карты.
## Бронирование, билет и перелет
Ниже **учебный пример**, придуманный для объяснения. Это не выгрузка из стенда.
Короткие обозначения `T1`, `F101`, `R1` заменяют настоящие номера.
Аня и Борис купили билеты в одном бронировании `B1` от 30 августа.
Аня летит 1 сентября из Москвы через Петербург в Казань. Борис летит
тем же первым рейсом до Петербурга. Все сегменты - Economy.
| Таблица источника | Пример строк | Что различают строки |
|---|---|---|
| `bookings.bookings` | `B1`, сумма `12000` | Одно бронирование на всю покупку |
| `bookings.tickets` | `T1 → B1, Аня`; `T2 → B1, Борис` | Два билета, по одному на пассажира |
| `bookings.segments` | `(T1, F101), 3000`; `(T1, F102), 5000`; `(T2, F101), 4000` | Три оплаченных перелета |
| `bookings.flights` | `F101 → R1`, 1 сентября; `F102 → R2`, 1 сентября | Два конкретных рейса |
| `bookings.routes` | `R1: SVO → LED`; `R2: LED → KZN` | Маршруты и варианты их расписания |
Бронирование объединяет покупку. Билет принадлежит пассажиру и может включать
несколько сегментов. Сегмент соединяет билет с конкретным рейсом и хранит цену
этого перелета. Поэтому `ticket_no` недостаточно для поиска сегмента:
нужна пара `(ticket_no, flight_id)`.
Рейс и маршрут тоже различаются. `flight_id` обозначает один вылет,
а `route_no` повторяется у рейсов в разные дни. В `routes` у одного номера
могут быть разные варианты расписания; строку различает пара
`(route_no, validity)`.
Откройте [билеты](architecture-map.html#node=bookings.tickets&view=keys)
в виде **"Связи по ключам"**. Выделены бронирование, сегменты и связанные с ними
талоны. Выберите связь билета с сегментами: в карточке виден `ticket_no`.
Для связи сегмента с талоном понадобятся оба поля ключа, иначе талон
первого рейса можно ошибочно присоединить ко второму.
<a id="как-устроен-поток-целиком"></a>
## Путь данных от источника до витрин
Оставьте билет выбранным и переключитесь на **"Поток данных"**.
Таблицы останутся на местах, выбранный билет сохранится; теперь стрелки
показывают чтение данных загрузчиками. Синий цвет отмечает источники выбранной
таблицы, зеленый - потребителей, оранжевый - сам выбор. Несвязанные объекты
остаются видны приглушенными. Повторный выбор, **"Общий вид"** или `Esc`
возвращает обзор.
Проследите путь
[сегмента](architecture-map.html#node=bookings.segments) через STG и ODS к факту:
- Источник → STG: PXF читает Postgres через внешние `stg.*_ext`.
Эти входы описаны в карточках STG и сами не хранят копию. Загрузчики
сохраняют строки в `stg.*` с бизнес-полями в TEXT и метками загрузки.
- STG → ODS: У строки появляются нужные типы. Например,
`stg.segments.price` становится `ods.segments.amount` типа NUMERIC.
ODS хранит текущее состояние строки по бизнес-ключу.
- ODS → DDS → DM: Сегмент обогащается ключами измерений в факте,
затем участвует в агрегатах витрины.
Стрелка данных не означает очередность задач Airflow. Например, календарь
генерируется без чтения ODS, хотя в DDS DAG задачи других измерений ждут
проверки календаря. Порядок выполнения смотрите по ссылке **"DAG · порядок задач"**
в карточке. Поля, SQL и проверки раскрываются там же, после выбора таблицы.
<a id="как-собирается-факт"></a>
## Сборка факта продаж
Откройте [dds.fact_flight_sales](architecture-map.html#node=dds.fact_flight_sales).
В нашем примере в нем будет **три строки**, по одной на каждый сегмент.
Важные значения до выполнения студенческих заданий выглядят так:
| ticket_no | flight_id | price | departure_airport_sk | arrival_airport_sk | route_sk | airplane_sk | passenger_sk |
|---|---|---:|---|---|---|---|---|
| T1 | F101 | 3000 | SK аэропорта SVO | SK аэропорта LED | NULL | NULL | NULL |
| T1 | F102 | 5000 | SK аэропорта LED | SK аэропорта KZN | NULL | NULL | NULL |
| T2 | F101 | 4000 | SK аэропорта SVO | SK аэропорта LED | NULL | NULL | NULL |
SK здесь обозначены словами: конкретные числа назначает загрузка измерений.
`calendar_sk` указывает на 1 сентября, `tariff_sk` - на Economy.
`book_ref = B1` повторяется во всех трех строках; `book_date` - 30 августа.
Цена хранится на уровне сегмента, а сумма `12000` из бронирования в факт
не переносится. Иначе ее можно было бы ошибочно посчитать трижды.
Начальный поток - `ods.segments`. Соединения с билетами, бронированиями
и рейсами - `INNER JOIN`: отсутствие подходящей строки исключит сегмент
из вставки факта. Это потеря покрытия, которую должна обнаружить DQ.
Посадочный талон добавляется через `LEFT JOIN` по паре `(ticket_no, flight_id)`.
Нет талона - сегмент сохраняется, `seat_no` остается `NULL`, `is_boarded = false`.
В этом стенде признак посадки означает именно наличие талона.
<a id="откуда-берутся-два-аэропорта"></a>
### Аэропорты вылета и прилета
В карточке факта откройте связи **"Коды двух аэропортов"**,
**"Аэропорт вылета"** и **"Аэропорт прилета"**:
1. Рейс дает `route_no`. Из `ods.routes` берется одна строка с наибольшим
`validity` на этот номер. В ODS поле `validity` хранится как TEXT;
этот выбор не использует дату рейса.
2. `departure_airport` и `arrival_airport` дают два кода аэропорта.
3. Один `dds.dim_airports` присоединяется дважды по `airport_bk`, в ролях
вылета и прилета. Получаются два разных поля SK в факте.
Этот путь работает без студенческого `dim_routes`. Отсутствующий аэропорт
в факте - аномалия данных, а не ожидаемый результат невыполненного задания.
SQL использует `LEFT JOIN`, поэтому строка с таким пропуском сохранится;
допустимость пропуска отдельно оценивает [DQ факта](../../sql/dds/fact_flight_sales_dq.sql).
<a id="какая-версия-маршрута-нужна-рейсу"></a>
### Версия маршрута на дату рейса
Связь **"Версия на дату рейса"** ведет к `dds.dim_routes` по `route_bk = route_no`
и дате `scheduled_departure::DATE`. Дата должна попасть в полуоткрытый
интервал `[valid_from, valid_to)`; `NULL` в конце означает открытую версию.
Рейс на границе двух версий относится к новой.
Из выбранной версии берется `route_sk`, а ее `airplane_code` нужен для
поиска `airplane_sk` в `dim_airplanes`. Поэтому одного заполненного справочника
моделей недостаточно: без подходящей версии маршрута `airplane_sk` не появится.
<a id="что-кормит-витрины"></a>
## Агрегация фактов в витрине продаж
Перейдите к [dm.sales_report](architecture-map.html#node=dm.sales_report).
Зерно меняется: день вылета × два аэропорта × класс обслуживания.
Три строки примера дадут две строки витрины:
| Дата вылета | Направление | Класс | tickets_sold | total_revenue |
|---|---|---|---:|---:|
| 1 сентября | SVO → LED | Economy | 2 | 7000 |
| 1 сентября | LED → KZN | Economy | 1 | 5000 |
`tickets_sold` здесь считает **сегменты**. Один билет может участвовать несколько раз.
Если сложить его по направлениям, получится 3 при двух билетах.
`total_revenue` складывается из цен сегментов, поэтому итог равен 12000.
Доля посадок рассчитывается по `is_boarded`.
Готовая витрина использует календарь, две роли аэропорта и тариф через
`INNER JOIN`: факт без совпадения в одном из этих измерений не попадет в агрегат.
Поля `route_sk`, `airplane_sk`, `passenger_sk` ей не требуются, поэтому
эталон работает до выполнения заданий. Остальные витрины видны рядом
пунктирными рамками: в их карточках описан требуемый результат по ТЗ.
## Модели самолетов и места: начало задания
Откройте [dds.dim_airplanes](architecture-map.html#node=dds.dim_airplanes).
У него два источника: `ods.airplanes` дает характеристики модели,
а `ods.seats` - места, по которым считается вместимость.
Строка измерения описывает **модель**, например тип самолета, а не отдельный борт.
`seat_no` уникален только вместе с `airplane_code`.
На `main` STG уже заполняется, а загрузки `ods.airplanes`, `ods.seats`
и `dds.dim_airplanes` пока содержат `SELECT 1;`. Карточки помечены
**"Задание"** и ведут к соответствующим пунктам ТЗ. Ожидаемые поля
известны из DDL, но готовой загрузки этих объектов еще нет.
## Ключевые договорённости
- Ключи на карте логические. В DDL таблиц DWH нет ограничений
`PRIMARY KEY`, `UNIQUE` и `FOREIGN KEY`; уникальность и связи проверяют SQL
и DQ. `DISTRIBUTED BY` отвечает за размещение строк.
- `_load_id` и `_load_ts` описывают загрузку. `event_ts` - ее опорное время;
у снимков справочников в STG оно равно времени получения снимка.
Подробности - в [соглашениях об именах](naming_conventions.md).
- До реализации измерений допустимы `NULL` в `passenger_sk`, `route_sk`
и `airplane_sk`. Ключи фиксируются **при вставке** факта: повторный запуск
меняет цену и сведения о посадке, но не заполняет старые SK.
После выполнения заданий следуйте разделу
["Пересчет факта после реализации измерений"](../assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений).
- В карточке заглушки описан результат из актуального ТЗ. Если ТЗ не определяет
тип соединения или поведение при пропуске строки, карта отмечает этот предел,
не выдает предположение за работающий SQL.
## Куда идти дальше
Для первого задания разберите готовую загрузку справочника:
[от карты к SQL загрузки](reading_the_pipeline.md#ods-airports), затем откройте
[первое задание ODS](../assignment/README.md#первая-загрузка-ods). Для точечного вопроса
о таблице возвращайтесь на карту: там находится каталог объектов,
их источников, потребителей, SQL и DQ.
Для проверки результата пригодится [справочник DQ](../reference/dq_taxonomy.md).
Технические материалы по слоям остаются доступны:
[STG](bookings_stg_design.md), [ODS](bookings_ods_design.md),
[DDS](bookings_dds_design.md), [DM](bookings_dm_design.md).
В них есть планы разработки; текущее поведение проверяйте по SQL,
а требования к самостоятельной работе - по ТЗ.