Руководства STG/ODS/DDS/DM сверены с действующими SQL и DAG, ожидания учебных заглушек связаны с ТЗ. Самопроверка заданий отделена от проверок разработки и исторических планов. Для dim_routes закреплено обновление описательных полей только у открытых версий. SQL, DAG, DDL и настройки стенда не менялись. Проверки: make lint; make test (4 passed, 14 skipped); карта; 292 локальные ссылки; маршрут чтения и карта в Chromium. Живой ETL не запускался.
17 KiB
Как устроено хранилище авиаперевозок
Разберем одну поездку: как бронирование связано с билетами и перелетами, какие строки попадут в факт и как сложится выручка по направлениям. Найти эти строки в структуре хранилища поможет интерактивная карта. Карта хранит подробности объектов и связей, а это руководство задает маршрут чтения.
Как открыть карту
Из локальной копии репозитория откройте docs/design/architecture-map.html
в браузере: через файловый менеджер или командой из корня проекта:
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).
Откройте билеты
в виде "Связи по ключам". Выделены бронирование, сегменты и связанные с ними
талоны. Выберите связь билета с сегментами: в карточке виден ticket_no.
Для связи сегмента с талоном понадобятся оба поля ключа, иначе талон
первого рейса можно ошибочно присоединить ко второму.
Путь данных от источника до витрин
Оставьте билет выбранным и переключитесь на "Поток данных".
Таблицы останутся на местах, выбранный билет сохранится; теперь стрелки
показывают чтение данных загрузчиками. Синий цвет отмечает источники выбранной
таблицы, зеленый - потребителей, оранжевый - сам выбор. Несвязанные объекты
остаются видны приглушенными. Повторный выбор, "Общий вид" или Esc
возвращает обзор.
Проследите путь сегмента через 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 и проверки раскрываются там же, после выбора таблицы.
Сборка факта продаж
Откройте 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.
В этом стенде признак посадки означает именно наличие талона.
Аэропорты вылета и прилета
В карточке факта откройте связи "Коды двух аэропортов", "Аэропорт вылета" и "Аэропорт прилета":
- Рейс дает
route_no. Изods.routesберется одна строка с наибольшимvalidityна этот номер. В ODS полеvalidityхранится как TEXT; этот выбор не использует дату рейса. departure_airportиarrival_airportдают два кода аэропорта.- Один
dds.dim_airportsприсоединяется дважды поairport_bk, в ролях вылета и прилета. Получаются два разных поля SK в факте.
Этот путь работает без студенческого dim_routes. Отсутствующий аэропорт
в факте - аномалия данных, а не ожидаемый результат невыполненного задания.
SQL использует LEFT JOIN, поэтому строка с таким пропуском сохранится;
допустимость пропуска отдельно оценивает DQ факта.
Версия маршрута на дату рейса
Связь "Версия на дату рейса" ведет к 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 не появится.
Агрегация фактов в витрине продаж
Перейдите к 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.
У него два источника: 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 оно равно времени получения снимка. Подробности - в соглашениях об именах.- До реализации измерений допустимы
NULLвpassenger_sk,route_skиairplane_sk. Ключи фиксируются при вставке факта: повторный запуск меняет цену и сведения о посадке, но не заполняет старые SK. После выполнения заданий следуйте разделу "Пересчет факта после реализации измерений". - В карточке заглушки описан результат из актуального ТЗ. Если ТЗ не определяет тип соединения или поведение при пропуске строки, карта отмечает этот предел, не выдает предположение за работающий SQL.
Куда идти дальше
Для первого задания разберите готовую загрузку справочника: от карты к SQL загрузки, затем откройте первое задание ODS. Для точечного вопроса о таблице возвращайтесь на карту: там находится каталог объектов, их источников, потребителей, SQL и DQ.
Проверяйте задание по инструкции самопроверки. Для разбора ошибки загрузки откройте руководство нужного слоя: STG, ODS, DDS, DM. Индекс документации ведет к справочникам, проверкам разработки и истории проектирования.