Шаг 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>
12 KiB
Дизайн STG для bookings в Greenplum
1. Цель и общий контур
- Источник: Postgres в контейнере
bookings-db, базаdemo, таблицаbookings.bookings. - Цель: показываем путь данных от операционной БД до сырого слоя DWH в Greenplum.
- В этом документе описываем часть
src (bookings-db) → STG (Greenplum). STG — входной слой; далее данные обрабатываются в ODS → DDS → DM (см. соответствующие design-документы).
Примечание: в текущей версии стенда слой stg содержит не только bookings, но и остальные таблицы потока
(tickets, airports, airplanes, routes, seats, flights, segments, boarding_passes).
Ниже логика разобрана на примере bookings, потому что на нём проще показать принципы инкремента и батчей.
Логика на уровне слоёв (по статье):
src: оперативная система (bookings-db, схемаbookings).stg: сырой слой в Greenplum, максимально близкий к источнику, без бизнес‑логики.- далее данные обрабатываются в слоях:
ods(см. bookings_ods_design.md),dds(см. bookings_dds_design.md),dm(см. bookings_dm_design.md).
2. Схема и таблицы в Greenplum
2.1. Схема
- Используем одну схему
stgв Greenplum. - В этой схеме будут:
- внешние таблицы PXF
*_extдля чтения изbookings-db; - внутренние таблицы STG
stg.*для долговременного хранения «сырых» данных.
- внешние таблицы PXF
2.2. Внешняя таблица (PXF)
- Имя таблицы:
stg.bookings_ext. - Назначение: «окно» в исходную таблицу
bookings.bookingsвbookings-dbчерез PXF (JDBC). - Типы колонок:
- можем использовать «родные» типы из
bookings.bookings(включая даты/числа); - задача внешней таблицы — корректно читать данные из источника, не заниматься приведением типов.
- можем использовать «родные» типы из
В текущей реализации аналогично созданы внешние таблицы *_ext и для остальных сущностей (см. sql/stg/*_ddl.sql).
DDL определён в sql/stg/bookings_ddl.sql и подключается из sql/ddl_gp.sql через \i (применяется через make ddl-gp).
2.3. Внутренняя таблица STG
- Имя таблицы:
stg.bookings. - Назначение: хранить сырые данные из источника для последующей обработки (ODS/DDS/витрины).
- Принципы моделирования:
- все бизнес‑колонки из
bookings.bookingsхраним какTEXT(как в примерах STG из статьи); - не делаем
UPDATE/DELETE, толькоINSERTновых записей; - бизнес‑колонки по названию совпадают с источником (чтобы проще было маппить).
- все бизнес‑колонки из
Технологические колонки:
event_ts TIMESTAMP— дата/время из источника, приведённая к TIMESTAMP:- используется как опорная колонка для инкрементальной загрузки;
- заполняется из опорной даты/времени, принятой для конкретной сущности (например, для
bookings— изbook_date).
_load_ts TIMESTAMP NOT NULL DEFAULT now()— когда запись была загружена в STG._load_id TEXT NOT NULL— идентификатор «пачки» (например,{{ run_id }}Airflow).- при необходимости позже можно добавить
src_system TEXT, если появятся другие источники.
Колонки‑бизнес‑ключи (booking_id и т.п.) храним как TEXT. В слое DDS позже можно будет ввести суррогатные ключи и нормализовать модель под витрины.
3. Инкрементальная загрузка
3.1. Опорное поле для инкремента
- Опорная колонка:
event_ts(внутреннее имя в STG). - Источник значения:
- для
bookingsиспользуемbook_dateизbookings.bookings(в демо‑БД это поле естественно “шагает” по дням); - при чтении через
stg.bookings_extприводим кTIMESTAMPи сохраняем вstg.bookings.event_ts.
- для
3.2. Правила определения full/delta
- При первом запуске, если таблица
stg.bookingsпуста:- считаем режим
full— загружаем все строки изstg.bookings_ext.
- считаем режим
- При последующих запусках:
- читаем
max(event_ts)изstg.bookingsза все предыдущие загрузки; - загружаем строки, где
event_tsбольше этой максимальной метки (верхняя граница по времени не задаётся).
- читаем
Таким образом, вся логика инкремента «замкнута» на один техно‑столбец event_ts, который студент потом сможет использовать и на следующих слоях (например, в CDC‑логике).
4. DAG’и Airflow (логика на уровне задач)
4.1. DAG для DDL
dag_id:bookings_stg_ddl(реализован вairflow/dags/bookings_stg_ddl.py).- Назначение: один раз (или при изменении схемы) создать необходимые объекты в Greenplum:
- схему
stg(если её ещё нет); - внешние таблицы
*_extи внутренние таблицы слояstgдля всех сущностей потока (см.sql/stg/*_ddl.sql).
- схему
- Этот DAG не загружает данные, только подготавливает структуру.
- Вся DDL‑логика (CREATE/ALTER/DROP) сосредоточена здесь; рабочие DAG’и занимаются только DML (INSERT/SELECT).
4.2. DAG для пошаговой загрузки
dag_id:bookings_to_gp_stage.- Основные параметры:
_load_id(в текущей реализации{{ run_id }}) — метка батча, которая попадает вstg.bookings._load_id;- подключения:
bookings_db_conn_id— Airflow connection кbookings-db(в коде DAG —BOOKINGS_CONN_ID = "bookings_db");greenplum_conn_id— Airflow connection к Greenplum (GREENPLUM_CONN_ID = "greenplum_conn").
Последовательность задач (упрощённая, но отражающая те же шаги):
generate_bookings_day- PostgresOperator к
bookings-db; - выполняет скрипт
/sql/src/bookings_generate_day_if_missing.sql; - скрипт смотрит на
max(book_date)и:- если база пуста — берёт стартовую дату из конфигурации (
bookings.start_date) и генерируетbookings.init_daysсуток; - если данные уже есть — добавляет один следующий учебный день после
max(book_date)(логика как вbookings/generate_next_day.sql).
- если база пуста — берёт стартовую дату из конфигурации (
- PostgresOperator к
load_bookings_to_stg- PostgresOperator к Greenplum;
- выполняет скрипт
/sql/stg/bookings_load.sql; - внутри SQL считается
max(event_ts)по «старым» батчам и по нему строится окно инкремента:- первая загрузка (full) — берём все строки из
stg.bookings_ext; - последующие загрузки — берём только записи, где
book_dateбольше предыдущего максимума (верхняя граница по дате не задаётся явно);
- первая загрузка (full) — берём все строки из
- при вставке заполняются тех.колонки
event_ts,_load_ts,_load_id.
check_row_counts- PostgresOperator к Greenplum;
- выполняет скрипт
/sql/stg/bookings_dq.sql; - скрипт заново считает окно инкремента по тем же правилам, что и загрузка, и сравнивает:
- количество строк в
stg.bookings_extсbook_dateпозже «старого» максимума, - количество строк в
stg.bookingsдля текущего_load_id;
- количество строк в
- при расхождении выполняет
RAISE EXCEPTIONс понятным текстом ошибки.
- Далее — загрузка и DQ для остальных таблиц потока (tickets, справочники, транзакции).
finish_summary- PythonOperator, который логирует итог выполнения DAG и напоминает, где смотреть детальные логи.
Таким образом, вся бизнес‑логика инкремента и проверок живёт в SQL‑скриптах, а DAG отвечает за оркестрацию и подключение к нужным БД. Для менти это хороший пример разделения ответственности между SQL и Python.
5. Связь с остальными документами
- Детали настройки PXF и часовых поясов — в ветке
solution(docs/reference/). sql/stg/bookings_ddl.sql— DDL для схемыstgи таблицstg.bookings_ext/stg.bookings(подключается изsql/ddl_gp.sqlи применяется черезmake ddl-gp).
Дальнейшая обработка данных описана в design-документах ODS/DDS/DM (см. раздел 5).
6. Требования к читаемости и комментариям
- DAG’и
bookings_stg_ddlиbookings_to_gp_stage— это учебный материал для менти. - В коде DAG’ов должны быть:
- понятные docstring на русском у всех функций и DAG;
- короткие комментарии рядом с нетривиальной логикой (особенно вокруг инкремента и идемпотентности);
- говорящие
task_idи названия функций, отражающие их роль в процессе.
- Цель: чтобы по одному только коду DAG студент мог восстановить архитектуру процесса и сопоставить её с теорией из статьи про моделирование DWH.