diff --git a/.env.example b/.env.example index 5680198..a7a5172 100644 --- a/.env.example +++ b/.env.example @@ -22,10 +22,10 @@ BOOKINGS_DB_NAME=bookings BOOKINGS_DB_PORT=5434 # Начальная дата модельного времени для генерации демобазы BOOKINGS_START_DATE=2017-01-01 -# Количество дней для первой генерации (держим малым, чтобы быстрее увидеть данные) -BOOKINGS_INIT_DAYS=1 -# Количество джобов генератора. В учебном стенде поддерживается только значение 1. -BOOKINGS_JOBS=1 +# Количество дней для генерации с нуля (make bookings-generate); для make bookings-init не используется +BOOKINGS_INIT_DAYS=60 +# Количество параллельных джобов генератора (1 = синхронно без dblink, 2+ = через dblink) +BOOKINGS_JOBS=2 # Greenplum Configuration GP_USER=gpadmin diff --git a/Makefile b/Makefile index 7c805bc..845a980 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,16 @@ SHELL := /bin/bash UV := uv PYTHON_VERSION := 3.11 DEMODB_REPO := https://github.com/postgrespro/demodb.git -DEMODB_COMMIT := d68de192850237719f09b47688d5f3fc94653ca6 -BOOKINGS_JOBS ?= 1 +DEMODB_COMMIT := 866e56f7fe54596a1d2a88f5f32f4aa3b2698121 +BOOKINGS_JOBS ?= 2 BOOKINGS_START_DATE ?= 2017-01-01 -BOOKINGS_INIT_DAYS ?= 1 +BOOKINGS_INIT_DAYS ?= 60 .PHONY: up stop down clean airflow-init logs gp-psql ddl-gp \ - bookings-check-jobs bookings-clone-demodb bookings-init bookings-psql bookings-generate-day \ + bookings-check-jobs bookings-clone-demodb bookings-init bookings-generate bookings-dump \ + bookings-psql bookings-generate-day \ dev-setup dev-sync dev-lock test lint fmt clean-venv build e2e-smoke e2e-etl +BOOKINGS_SEED := bookings/seed/demo.sql.xz SHELL := /bin/bash up: @@ -52,25 +54,35 @@ bookings-clone-demodb: # Патчим generate/continue: при jobs=1 запускаем process_queue синхронно, без dblink if ! grep -q "Job 1 (local): ok" bookings/demodb/engine.sql; then \ if ! patch -d bookings/demodb -p1 --forward < bookings/patches/engine_jobs1_sync.patch; then \ - echo "Не удалось применить патч engine_jobs1_sync.patch. Удалите bookings/demodb и повторите make bookings-init." >&2; \ + echo "Не удалось применить патч engine_jobs1_sync.patch. Удалите bookings/demodb и повторите make bookings-generate." >&2; \ exit 1; \ fi; \ fi # Делаем установку идемпотентной и принудительной: DROP DATABASE IF EXISTS demo WITH (FORCE) if ! grep -q "DROP DATABASE IF EXISTS demo WITH (FORCE);" bookings/demodb/install.sql; then \ if ! patch -d bookings/demodb -p1 --forward < bookings/patches/install_drop_if_exists.patch; then \ - echo "Не удалось применить патч install_drop_if_exists.patch. Удалите bookings/demodb и повторите make bookings-init." >&2; \ + echo "Не удалось применить патч install_drop_if_exists.patch. Удалите bookings/demodb и повторите make bookings-generate." >&2; \ + exit 1; \ + fi; \ + fi + # Убираем хардкод gen.connstr без credentials — иначе VACUUM падает + if grep -q "gen.connstr = 'dbname=demo'" bookings/demodb/install.sql; then \ + if ! patch -d bookings/demodb -p1 --forward < bookings/patches/install_connstr_no_hardcode.patch; then \ + echo "Не удалось применить патч install_connstr_no_hardcode.patch. Удалите bookings/demodb и повторите make bookings-generate." >&2; \ exit 1; \ fi; \ fi bookings-check-jobs: - @if [ "$(BOOKINGS_JOBS)" != "1" ]; then \ - echo "Поддерживается только BOOKINGS_JOBS=1. Измените .env и повторите команду." >&2; \ + @case "$(BOOKINGS_JOBS)" in \ + ''|*[!0-9]*) echo "BOOKINGS_JOBS должен быть целым числом >= 1. Текущее значение: '$(BOOKINGS_JOBS)'." >&2; exit 1;; \ + esac; \ + if [ "$(BOOKINGS_JOBS)" -lt 1 ]; then \ + echo "BOOKINGS_JOBS должен быть >= 1. Текущее значение: $(BOOKINGS_JOBS)." >&2; \ exit 1; \ fi -bookings-init: bookings-check-jobs bookings-clone-demodb +bookings-generate: bookings-check-jobs bookings-clone-demodb docker compose -f docker-compose.yml up -d bookings-db docker compose -f docker-compose.yml exec bookings-db bash -lc '\ until PGPASSWORD="$$POSTGRES_PASSWORD" pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -h localhost; do \ @@ -89,10 +101,96 @@ bookings-init: bookings-check-jobs bookings-clone-demodb ALTER DATABASE demo SET bookings.init_days = '\''$(BOOKINGS_INIT_DAYS)'\''; \ ALTER DATABASE demo SET bookings.jobs = '\''$(BOOKINGS_JOBS)'\'';" \ ' - # Генерируем первый день данных, чтобы база не оставалась пустой + # Генерируем данные за $(BOOKINGS_INIT_DAYS) дней docker compose -f docker-compose.yml exec bookings-db bash -lc '\ PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d demo -f /bookings/generate_next_day.sql \ ' + # Валидация: ключевые таблицы не должны быть пустыми + @docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d demo -tAc " \ + SELECT format(E'\''%-25s %s'\'', t, cnt) \ + FROM ( \ + SELECT '\''bookings.bookings'\'' AS t, count(*) AS cnt FROM bookings.bookings \ + UNION ALL \ + SELECT '\''bookings.tickets'\'', count(*) FROM bookings.tickets \ + UNION ALL \ + SELECT '\''bookings.flights'\'', count(*) FROM bookings.flights \ + UNION ALL \ + SELECT '\''bookings.boarding_passes'\'', count(*) FROM bookings.boarding_passes \ + ) s ORDER BY t; \ + " \ + ' + @docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d demo -tAc " \ + DO \$$\$$ \ + DECLARE v_cnt bigint; \ + BEGIN \ + SELECT count(*) INTO v_cnt FROM bookings.bookings; \ + IF v_cnt = 0 THEN \ + RAISE EXCEPTION '\''bookings.bookings пуста — генерация не сработала. Проверьте логи: SELECT * FROM gen.log ORDER BY at DESC LIMIT 10;'\''; \ + END IF; \ + SELECT count(*) INTO v_cnt FROM bookings.flights; \ + IF v_cnt = 0 THEN \ + RAISE EXCEPTION '\''bookings.flights пуста — попробуйте увеличить BOOKINGS_INIT_DAYS.'\''; \ + END IF; \ + END \$$\$$; \ + " \ + ' + +bookings-init: bookings-check-jobs + @if [ ! -f $(BOOKINGS_SEED) ]; then \ + echo "Файл $(BOOKINGS_SEED) не найден. Используйте make bookings-generate для генерации с нуля." >&2; \ + exit 1; \ + fi + docker compose -f docker-compose.yml up -d bookings-db + docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + until PGPASSWORD="$$POSTGRES_PASSWORD" pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -h localhost; do \ + echo "Waiting for bookings-db to become ready..."; \ + sleep 1; \ + done \ + ' + docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" \ + -c "DROP DATABASE IF EXISTS demo WITH (FORCE);" \ + ' + @echo "Восстановление из дампа (~42 MB)..." + xz -dc $(BOOKINGS_SEED) | docker compose -f docker-compose.yml exec -T bookings-db bash -lc '\ + PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" \ + ' + # Применяем настройки из текущего окружения поверх дампа + docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + CONNSTR="dbname=demo user=$$POSTGRES_USER password=$$POSTGRES_PASSWORD"; \ + PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -c "\ + ALTER DATABASE demo SET gen.connstr='\''$$CONNSTR'\''; \ + ALTER DATABASE demo SET bookings.start_date = '\''$(BOOKINGS_START_DATE)'\''; \ + ALTER DATABASE demo SET bookings.init_days = '\''$(BOOKINGS_INIT_DAYS)'\''; \ + ALTER DATABASE demo SET bookings.jobs = '\''$(BOOKINGS_JOBS)'\'';" \ + ' + @echo "Восстановление завершено. Проверяем данные..." + @docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + PGPASSWORD="$$POSTGRES_PASSWORD" psql -U "$$POSTGRES_USER" -d demo -tAc " \ + SELECT format(E'\''%-25s %s'\'', t, cnt) \ + FROM ( \ + SELECT '\''bookings.bookings'\'' AS t, count(*) AS cnt FROM bookings.bookings \ + UNION ALL \ + SELECT '\''bookings.tickets'\'', count(*) FROM bookings.tickets \ + UNION ALL \ + SELECT '\''bookings.flights'\'', count(*) FROM bookings.flights \ + UNION ALL \ + SELECT '\''bookings.boarding_passes'\'', count(*) FROM bookings.boarding_passes \ + ) s ORDER BY t; \ + " \ + ' + +bookings-dump: + @echo "Создание дампа demo → $(BOOKINGS_SEED)..." + mkdir -p bookings/seed + docker compose -f docker-compose.yml exec bookings-db bash -lc '\ + PGPASSWORD="$$POSTGRES_PASSWORD" pg_dump -U "$$POSTGRES_USER" -d demo --format=plain --create \ + ' > /tmp/demo_dump_$$$$.sql + xz -9 < /tmp/demo_dump_$$$$.sql > $(BOOKINGS_SEED) + rm -f /tmp/demo_dump_$$$$.sql + @echo "Дамп сохранён: $$(ls -lh $(BOOKINGS_SEED) | awk '{print $$5}')" bookings-psql: docker compose -f docker-compose.yml exec bookings-db bash -lc 'PGPASSWORD="$$POSTGRES_PASSWORD" psql -U "$$POSTGRES_USER" -d demo' diff --git a/README.md b/README.md index 0b9e128..85fb6e1 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ make up 3) Инициализируйте демо‑БД bookings: ```bash -make bookings-init +make bookings-init # быстрое восстановление из seed-дампа (~18 сек), рекомендуется +# make bookings-generate # альтернатива: полная генерация с нуля (занимает часы) ``` 4) Подготовьте STG/ODS/DDS-объекты в Greenplum (выберите один вариант): @@ -148,8 +149,8 @@ make clean # полный reset: удалить контейнер | Проблема | Решение | |----------|---------| | Airflow UI не открывается | Дождитесь сообщения `Listening at: http://0.0.0.0:8080` в логах (`make logs`) | -| `database "demo" does not exist` в bookings‑DAG | Вы сделали reset с удалением volumes (`make clean` / `docker compose down -v`). Запустите `make bookings-init` и повторите DAG. | -| `Поддерживается только bookings.jobs=1` или `BOOKINGS_JOBS=1` | В этом учебном стенде поддерживается только синхронный режим генерации. Установите `BOOKINGS_JOBS=1` и выполните `make bookings-init`. | +| `database "demo" does not exist` в bookings‑DAG | Вы сделали reset с удалением volumes (`make clean` / `docker compose down -v`). Запустите `make bookings-init` (быстрое восстановление из дампа, ~18 сек) и повторите DAG. | +| Ошибка `bookings.jobs должен быть >= 1` | Проверьте значение `BOOKINGS_JOBS` в `.env` — оно должно быть целым числом >= 1. По умолчанию `BOOKINGS_JOBS=2` (параллельная генерация через dblink). При `BOOKINGS_JOBS=1` генерация идёт синхронно. | | Ошибка подключения к Greenplum | Убедитесь, что контейнер `greenplum` имеет статус `healthy` (`docker compose ps`) | | `protocol "pxf" does not exist` | Перезапустите `greenplum` и повторите `bookings_stg_ddl`/`make ddl-gp` — расширение `pxf` создаётся автоматически при старте контейнера. | | DAG `bookings_to_gp_stage` ругается на отсутствующие таблицы stg | Запустите `bookings_stg_ddl` (или выполните `make ddl-gp`), затем повторите запуск | diff --git a/TESTING.md b/TESTING.md index f1370e1..80f9480 100644 --- a/TESTING.md +++ b/TESTING.md @@ -30,13 +30,13 @@ - `greenplum_conn` и `bookings_db` задаются через переменные `AIRFLOW_CONN_...` в docker-compose и могут не отображаться в списке, но `airflow connections get greenplum_conn` / `bookings_db` внутри контейнера должны отрабатывать без ошибок. - DAG `bookings_to_gp_stage` (полная проверка цепочки bookings → Greenplum STG): - - предварительно выполнить один раз: `make bookings-init` (установка демобазы `demo` в контейнере `bookings-db`) и `make ddl-gp` (создаёт STG/ODS/DDS слои в Greenplum, включая внешние `*_ext` через PXF); + - предварительно выполнить один раз: `make bookings-init` (быстрое восстановление демобазы `demo` из seed-дампа, ~18 сек) и `make ddl-gp` (создаёт STG/ODS/DDS слои в Greenplum, включая внешние `*_ext` через PXF); - перед Trigger проверить, что в source реально есть данные (все значения должны быть `> 0`): - `docker compose exec bookings-db psql -U bookings -d demo -At -c "SELECT COUNT(*) FROM bookings.bookings;"` - `docker compose exec bookings-db psql -U bookings -d demo -At -c "SELECT COUNT(*) FROM bookings.airports_data;"` - `docker compose exec bookings-db psql -U bookings -d demo -At -c "SELECT COUNT(*) FROM bookings.airplanes_data;"` - если хотя бы один `COUNT(*) = 0`, не запускать DAG: повторить `make bookings-init`; если после этого `bookings.bookings` всё ещё пустая, выполнить `make bookings-generate-day` и снова проверить `COUNT(*)`; - - важно: DAG `bookings_stg_ddl` **не** создаёт базу `demo` в `bookings-db`; если вы делали `docker compose down -v` / `make clean`, `make bookings-init` обязателен; + - важно: DAG `bookings_stg_ddl` **не** создаёт базу `demo` в `bookings-db`; если вы делали `docker compose down -v` / `make clean`, `make bookings-init` обязателен (быстрое восстановление из seed-дампа); - включить DAG `bookings_to_gp_stage` и запустить `Trigger DAG`; - убедиться, что все задачи завершились со статусом Success (включая загрузки справочников/транзакций и DQ); - при желании проверить данные: в `bookings-db` появился новый день, а в Greenplum в `stg.bookings` — строки с актуальным `batch_id` (см. пример запросов в разделе 5). diff --git a/TODO.md b/TODO.md index 5b20d21..091ebb0 100644 --- a/TODO.md +++ b/TODO.md @@ -28,7 +28,7 @@ + ручное тестирование (make up, запуск DAG'ов, проверка данных). - [ ] Протестировать полный ETL-цикл с нуля - (make up → bookings-init → STG → ODS → DDS → DM) + (make up → bookings-init (восстановление из дампа) → STG → ODS → DDS → DM) - [ ] Прогнать инкремент (bookings-generate-day → повторный запуск DAG'ов) - [ ] Почистить код эталонного среза - [ ] Актуализировать README и документацию @@ -119,7 +119,7 @@ - диагностика текущего кейса: `docs/internal/pxf_bookings.md` (раздел «Известная проблема»). - [x] Разобраться с генератором demodb: - - после `make bookings-init` таблица `bookings.bookings` остаётся пустой; + - после `make bookings-generate` таблица `bookings.bookings` остаётся пустой; - патчи `bookings/patches/engine_jobs1_sync.patch` и `bookings/patches/install_drop_if_exists.patch` падают при применении (hunk failed / garbage in patch); - из‑за этого DAG `bookings_to_gp_stage` валится на проверках (источник пустой). diff --git a/bookings/README.md b/bookings/README.md index 30dac82..dd8884b 100644 --- a/bookings/README.md +++ b/bookings/README.md @@ -4,29 +4,38 @@ На первом этапе мы: - поднимаем отдельный контейнер `bookings-db` с Postgres; -- устанавливаем в нём генератор демобазы `demodb` (репозиторий `postgrespro/demodb`); +- инициализируем демобазу одним из двух способов (см. ниже); - генерируем данные «день за днём» с помощью `make`‑команд. -Основные команды см. в корневом `Makefile` (`bookings-init`, `bookings-generate-day`, `bookings-psql`) и в `README.md` проекта. +### Два способа инициализации + +| Команда | Что делает | Время | Для кого | +|---------|-----------|-------|----------| +| `make bookings-init` | Быстрое восстановление из seed-дампа | ~18 сек | **Студенты** (рекомендуется по умолчанию) | +| `make bookings-generate` | Полная генерация с нуля через генератор demodb | часы | Разработчики, пересоздание дампа | + +Основные команды см. в корневом `Makefile` (`bookings-init`, `bookings-generate`, `bookings-generate-day`, `bookings-psql`) и в `README.md` проекта. ## Источник и версия - Репозиторий демобазы: `postgrespro/demodb`. -- Закреплённый коммит: `d68de192850237719f09b47688d5f3fc94653ca6` (см. `DEMODB_COMMIT` в корневом `Makefile`). +- Закреплённый коммит: `866e56f7` (см. `DEMODB_COMMIT` в корневом `Makefile`). ## Что мы патчим в demodb - `install.sql`: `DROP DATABASE IF EXISTS demo WITH (FORCE)` — установка не падает, даже если демобазу держат активные сессии (например, из Airflow). - `engine.sql`: два изменения в `engine_jobs1_sync.patch`: - `busy()` игнорирует свой `pid`, чтобы не считать собственное подключение занятым; - `continue()` при `jobs=1` вызывает `process_queue` синхронно (без `dblink`), иначе генерация обрывается при выходе из `psql` и данных не появляется. -- Режим эксплуатации в этом стенде: только `jobs=1` (`BOOKINGS_JOBS=1`). -- Патчи применяются автоматически в `make bookings-init`. Если что-то пошло не так, их можно накатить вручную: +- `install.sql`: удалён хардкод `gen.connstr` без credentials (`install_connstr_no_hardcode.patch`). +- Дефолт: `BOOKINGS_JOBS=2`. При `jobs=1` генерация синхронная (без dblink), при `jobs>1` — через dblink. +- Патчи применяются автоматически в `make bookings-generate` (генерация с нуля). Если что-то пошло не так, их можно накатить вручную: ``` patch -d bookings/demodb -p1 --forward < bookings/patches/install_drop_if_exists.patch patch -d bookings/demodb -p1 --forward < bookings/patches/engine_jobs1_sync.patch ``` ## Быстрая проверка после init/обновления -- `make bookings-init` должен завершиться без ошибок; в `bookings.bookings` ожидаем >0 строк (примерно 15k). +- `make bookings-init` (восстановление из дампа) должен завершиться без ошибок; в `bookings.bookings` ожидаем >0 строк (примерно 15k). +- `make bookings-generate` (генерация с нуля) тоже должен дать >0 строк, но занимает значительно больше времени. - `make bookings-generate-day` добавляет следующий день после `max(book_date)`. - Ручной вызов генерации из psql/DBeaver — только через DO-блок (подзапрос в аргументах `CALL` не работает): ```sql diff --git a/bookings/generate_next_day.sql b/bookings/generate_next_day.sql index 236c986..db8ea42 100644 --- a/bookings/generate_next_day.sql +++ b/bookings/generate_next_day.sql @@ -11,12 +11,11 @@ DECLARE BEGIN -- Проверяем, что демобаза установлена IF to_regclass('bookings.bookings') IS NULL THEN - RAISE EXCEPTION 'Таблица bookings.bookings не найдена. Сначала выполните make bookings-init.'; + RAISE EXCEPTION 'Таблица bookings.bookings не найдена. Сначала выполните make bookings-init или make bookings-generate.'; END IF; - -- В учебном стенде поддерживается только jobs=1, иначе генерация нестабильна. - IF v_jobs <> 1 THEN - RAISE EXCEPTION 'Поддерживается только bookings.jobs=1. Текущее значение: %. Установите BOOKINGS_JOBS=1 и выполните make bookings-init.', v_jobs; + IF v_jobs < 1 THEN + RAISE EXCEPTION 'bookings.jobs должен быть >= 1. Текущее значение: %.', v_jobs; END IF; -- Ищем последнюю сгенерированную дату @@ -39,6 +38,17 @@ BEGIN CALL continue(v_end_date, v_jobs); END IF; + -- continue() делает TRUNCATE gen.stat_jobs → AccessExclusiveLock. + -- Без COMMIT воркеры не могут INSERT INTO gen.stat_jobs → deadlock. + COMMIT; + + -- При jobs>1 воркерам нужно время, чтобы подключиться через dblink и + -- выставить application_name='Airlines processor'. Без паузы busy() + -- сразу вернёт false (воркеры ещё не видны в pg_stat_activity). + IF v_jobs > 1 THEN + PERFORM pg_sleep(3); + END IF; + -- Ждём завершения фоновых джобов генератора, чтобы данные успели записаться WHILE busy() LOOP PERFORM pg_sleep(1); @@ -50,4 +60,12 @@ BEGIN IF v_bookings_cnt = 0 THEN RAISE EXCEPTION 'Генератор demodb завершился, но bookings.bookings пустая. Проверьте применение патчей и логи генератора.'; END IF; + + -- Генерация закончена — возвращаем synchronous_commit = on и сбрасываем + -- буферы на диск. Без этого docker compose down может убить PostgreSQL + -- до записи WAL → данные пропадут (особенно на WSL2). + ALTER DATABASE demo SET synchronous_commit = on; + SET synchronous_commit = on; END $$; + +CHECKPOINT; diff --git a/bookings/patches/install_connstr_no_hardcode.patch b/bookings/patches/install_connstr_no_hardcode.patch new file mode 100644 index 0000000..c123c2c --- /dev/null +++ b/bookings/patches/install_connstr_no_hardcode.patch @@ -0,0 +1,14 @@ +--- a/install.sql ++++ b/install.sql +@@ -24,8 +24,9 @@ + -- use UTC to avoid daylight-saving problems + ALTER DATABASE demo SET timezone = 'Etc/UTC'; + +--- connection string for dblink +-ALTER DATABASE demo SET gen.connstr = 'dbname=demo'; ++-- Строку подключения для dblink (gen.connstr) задаёт Makefile ++-- с credentials текущего пользователя БД. Не хардкодим здесь, ++-- иначе VACUUM и другие dblink-вызовы падают без пароля. + + -- airlines company name + ALTER DATABASE demo SET gen.airlines_name = 'PostgresPro'; diff --git a/bookings/seed/demo.sql.xz b/bookings/seed/demo.sql.xz new file mode 100644 index 0000000..e2cd760 Binary files /dev/null and b/bookings/seed/demo.sql.xz differ diff --git a/docs/internal/bookings_db_issues.md b/docs/internal/bookings_db_issues.md index fcee629..cf7d526 100644 --- a/docs/internal/bookings_db_issues.md +++ b/docs/internal/bookings_db_issues.md @@ -1,7 +1,7 @@ # Проблемы bookings-db (demodb) > Дата обнаружения: 2026-03-08 -> Зафиксированный коммит demodb: `d68de192850237719f09b47688d5f3fc94653ca6` +> Зафиксированный коммит demodb: `866e56f7fe54596a1d2a88f5f32f4aa3b2698121` --- @@ -9,7 +9,7 @@ ### Симптом -После `make bookings-init` таблицы в demo-базе существуют, но **пустые**. +После `make bookings-generate` (генерация с нуля) таблицы в demo-базе существуют, но **пустые**. Повторные `make bookings-generate-day` тоже дают 0 строк. ### Корневая причина @@ -22,7 +22,7 @@ ALTER DATABASE demo SET gen.connstr = 'dbname=demo'; Без `user` и `password`. Makefile устанавливает правильный connstr (с `user=bookings password=bookings`) **после** `install.sql`, но при повторном -`make bookings-init` порядок тот же: install.sql перезаписывает → Makefile +`make bookings-generate` порядок тот же: install.sql перезаписывает → Makefile восстанавливает. Если что-то идёт не так между этими шагами, connstr остаётся без credentials. @@ -47,7 +47,7 @@ SELECT * FROM gen.log ORDER BY at DESC LIMIT 5; ### Воркэраунд ```sql --- Выполнить на bookings-db ПОСЛЕ install.sql: +-- Выполнить на bookings-db ПОСЛЕ install.sql (актуально при make bookings-generate): ALTER DATABASE demo SET gen.connstr = 'dbname=demo user=bookings password=bookings'; -- Затем переподключиться к demo и запустить генерацию заново. ``` @@ -135,9 +135,9 @@ Boarding passes создаются при событиях CHECK-IN и BOARDING, ## Рекомендации (TODO) -- [ ] Обновить demodb до последнего коммита — проверить, исправлены ли баги -- [ ] Добавить патч для gen.connstr (если не исправлено upstream) -- [ ] Увеличить BOOKINGS_INIT_DAYS до 30-60 -- [ ] Добавить валидацию после init (`make bookings-init` проверяет count > 0) +- [x] Обновить demodb до последнего коммита (`866e56f`) — upstream только README-правки, баги не исправлены +- [x] Добавить патч для gen.connstr (`install_connstr_no_hardcode.patch`) — убирает хардкод без credentials +- [x] Увеличить BOOKINGS_INIT_DAYS до 60 (в Makefile и .env) +- [x] Добавить валидацию после init (count > 0 для bookings, flights; вывод counts всех таблиц) - [ ] Решить проблему UX с временем генерации (дамп или prebuilt image) -- [ ] Проверить, появляются ли boarding_passes при init_days=90 + исправленном connstr +- [ ] Проверить, появляются ли boarding_passes при init_days=60 + исправленном connstr (нужен запуск стенда) diff --git a/docs/internal/bookings_tz.md b/docs/internal/bookings_tz.md index 683e2bb..fe0c41a 100644 --- a/docs/internal/bookings_tz.md +++ b/docs/internal/bookings_tz.md @@ -4,19 +4,23 @@ _Внутренний файл для наставника: поясняет, к - Контейнер `bookings-db` — отдельный сервис Postgres из `docker-compose.yml`, база по умолчанию `demo` (из upstream demodb), без переименований. - Доступ снаружи не блокируем (порт `5434` по умолчанию), чтобы позже читать через PXF и подключаться из Greenplum. -- Инициализация (`make bookings-init`): поднимает контейнер, клонирует demodb с закреплённым коммитом, накладывает патчи (`engine`: `jobs=1` синхронно + `busy()` игнорирует свой pid; `install.sql`: `DROP DATABASE IF EXISTS`), ждёт `pg_isready`, ставит `gen.connstr` и GUC `bookings.start_date/init_days/jobs`, затем запускает `/bookings/generate_next_day.sql` через `psql -f`. Значения по умолчанию: стартовая дата 2017-01-01, `init_days=1`, `jobs=1`. +- Инициализация: два способа: + - `make bookings-init` (рекомендуется): быстрое восстановление из seed-дампа (~18 сек). + - `make bookings-generate` (для разработчиков): полная генерация с нуля — клонирует demodb с закреплённым коммитом, накладывает патчи (`engine`: `jobs=1` синхронно + `busy()` игнорирует свой pid; `install.sql`: `DROP DATABASE IF EXISTS`, `connstr` без хардкода), ждёт `pg_isready`, ставит `gen.connstr` и GUC `bookings.start_date/init_days/jobs`, затем запускает `/bookings/generate_next_day.sql` через `psql -f`. Значения по умолчанию: стартовая дата 2017-01-01, `init_days=60`, `jobs=2`. - Генерация следующего дня: `make bookings-generate-day` прогоняет тот же SQL (читает GUC, вызывает `generate/continue`, ждёт `busy()`, закрывает dblink). При `jobs=1` всё синхронно, без dblink. -- Исходники demodb: клонируем по требованию с фиксированным хешем, кладём в `bookings/demodb/` (в `.gitignore`), патчи лежат в `bookings/patches/` и применяются автоматически. -- Документация: в README описаны команды (`bookings-init`, проверка данных, генерация дня), параметры `.env`; настройка PXF/ETL — следующий этап. +- Исходники demodb: клонируем по требованию с фиксированным хешем, кладём в `bookings/demodb/` (в `.gitignore`), патчи лежат в `bookings/patches/` и применяются автоматически при `make bookings-generate`. +- Документация: в README описаны команды (`bookings-init`, `bookings-generate`, проверка данных, генерация дня), параметры `.env`; настройка PXF/ETL — следующий этап. ## Текущее состояние -- `Makefile` теперь автоматически применяет патчи (`engine_jobs1_sync.patch`, `install_drop_if_exists.patch`), ждёт готовности Postgres через `pg_isready`, запускает `install.sql`, выставляет `gen.connstr`/GUC и вызывает `generate_next_day.sql` через `psql -f`. -- Дефолты: `BOOKINGS_START_DATE=2017-01-01`, `BOOKINGS_INIT_DAYS=1`, `BOOKINGS_JOBS=1`. При `jobs=1` генерация идёт синхронно без dblink, `busy()` не учитывает текущую сессию. +- `make bookings-init` — быстрое восстановление из seed-дампа (~18 сек), рекомендуется для студентов. +- `make bookings-generate` — полная генерация с нуля: автоматически применяет патчи (`engine_jobs1_sync.patch`, `install_drop_if_exists.patch`), ждёт готовности Postgres через `pg_isready`, запускает `install.sql`, выставляет `gen.connstr`/GUC и вызывает `generate_next_day.sql` через `psql -f`. +- Дефолты: `BOOKINGS_START_DATE=2017-01-01`, `BOOKINGS_INIT_DAYS=60`, `BOOKINGS_JOBS=2`. При `jobs=1` генерация идёт синхронно без dblink, `busy()` не учитывает текущую сессию. - `.env.example`/README обновлены под новые дефолты; каталог `bookings/demodb/` в `.gitignore`. - Патчи лежат в `bookings/patches/` и накладываются при `bookings-clone-demodb`. ## Текущее состояние тестов/проблем -- Чистый прогон `make bookings-init` (после `docker compose down -v` и удаления `bookings/demodb`) проходит за ~1,5 минуты: база ставится, `busy()` → `f`, `bookings.bookings` от `2017-01-01 00:00:18` до `2017-01-01 23:59:59`. +- Чистый прогон `make bookings-init` (восстановление из seed-дампа) проходит за ~18 секунд. +- Чистый прогон `make bookings-generate` (после `docker compose down -v` и удаления `bookings/demodb`) проходит за ~1,5 минуты: база ставится, `busy()` → `f`, `bookings.bookings` от `2017-01-01 00:00:18` до `2017-01-01 23:59:59`. - Ранее зависание на `busy()` при `jobs=1` лечится патчем: `process_queue` теперь синхронный, а `busy()` игнорирует текущий backend. - Данных пока только на 1 день по умолчанию, чтобы генерация не занимала много времени. diff --git a/docs/stack.md b/docs/stack.md index e446a3a..0272f42 100644 --- a/docs/stack.md +++ b/docs/stack.md @@ -131,8 +131,8 @@ make fmt - `BOOKINGS_DB_USER`, `BOOKINGS_DB_PASSWORD` - `BOOKINGS_DB_PORT` — внешний порт (по умолчанию `5434`) - `BOOKINGS_START_DATE` — стартовая дата модельного времени -- `BOOKINGS_INIT_DAYS` — сколько дней генерировать при первом `make bookings-init` -- `BOOKINGS_JOBS` — число джобов генератора; в учебном стенде поддерживается только `1` +- `BOOKINGS_INIT_DAYS` — сколько дней генерировать при `make bookings-generate` (генерация с нуля) +- `BOOKINGS_JOBS` — число джобов генератора (по умолчанию `2`) ### CSV pipeline (побочный пример) diff --git a/educational-tasks.md b/educational-tasks.md index b418890..031f956 100644 --- a/educational-tasks.md +++ b/educational-tasks.md @@ -49,7 +49,7 @@ - логическая дата запуска DAG (`{{ ds }}`) не влияет на выбор дня генерации; - скрипт всегда смотрит на `max(book_date)` и добавляет **следующий** день (или несколько стартовых дней, если база пуста). 4. Сделайте вывод: генератор всегда «шагает» по датам вперёд от максимальной даты, поэтому: - - при `make bookings-init` вы получаете `BOOKINGS_INIT_DAYS` дней начиная с `BOOKINGS_START_DATE`; + - при `make bookings-init` вы получаете готовые данные из seed-дампа (при `make bookings-generate` генератор создаст `BOOKINGS_INIT_DAYS` дней начиная с `BOOKINGS_START_DATE`); - при последующих вызовах (`make bookings-generate-day` или DAG) добавляется ровно один новый день. --- diff --git a/plans/bookings-demodb-bugfix-plan.md b/plans/bookings-demodb-bugfix-plan.md index 5cd35b4..0813ac1 100644 --- a/plans/bookings-demodb-bugfix-plan.md +++ b/plans/bookings-demodb-bugfix-plan.md @@ -2,18 +2,20 @@ ## Контекст -В стенде используется демобаза bookings из репозитория `postgrespro/demodb`, закреплённая на коммите `d68de192850237719f09b47688d5f3fc94653ca6` (см. `DEMODB_COMMIT` в `Makefile`). +В стенде используется демобаза bookings из репозитория `postgrespro/demodb`, закреплённая на коммите `866e56f7fe54596a1d2a88f5f32f4aa3b2698121` (см. `DEMODB_COMMIT` в `Makefile`). -Инициализация источника для ETL выполняется командой `make bookings-init`: -- клонирует demodb в `bookings/demodb/`; -- пытается применить патчи из `bookings/patches/`; -- запускает `install.sql` в контейнере `bookings-db`; -- выставляет GUC-параметры (`gen.connstr`, `bookings.start_date/init_days/jobs`); -- запускает `/bookings/generate_next_day.sql` (должен сгенерировать минимум 1 день данных). +Инициализация источника для ETL выполняется двумя способами: +- `make bookings-init` — быстрое восстановление из seed-дампа (~18 сек, рекомендуется для студентов); +- `make bookings-generate` — полная генерация с нуля (для разработчиков): + - клонирует demodb в `bookings/demodb/`; + - пытается применить патчи из `bookings/patches/`; + - запускает `install.sql` в контейнере `bookings-db`; + - выставляет GUC-параметры (`gen.connstr`, `bookings.start_date/init_days/jobs`); + - запускает `/bookings/generate_next_day.sql` (должен сгенерировать минимум 1 день данных). ## Симптомы (как в TODO) -- после `make bookings-init` таблица `bookings.bookings` остаётся пустой; +- после `make bookings-generate` таблица `bookings.bookings` остаётся пустой; - патчи `bookings/patches/engine_jobs1_sync.patch` и `bookings/patches/install_drop_if_exists.patch` падают при применении; - из‑за этого DAG `bookings_to_gp_stage` валится на проверках (источник пустой). @@ -24,11 +26,11 @@ 2) `install_drop_if_exists.patch` устарел относительно закреплённого коммита demodb: в `install.sql` уже есть `DROP DATABASE IF EXISTS demo;`, поэтому hunk “не находится” и патч не накатывается. -3) Ошибки патча сейчас замаскированы в `Makefile` через `|| true`, поэтому `make bookings-init` может завершаться “успешно”, хотя критичные правки в demodb не применились. +3) Ошибки патча сейчас замаскированы в `Makefile` через `|| true`, поэтому `make bookings-generate` может завершаться “успешно”, хотя критичные правки в demodb не применились. ## Цель фикса -- `make bookings-init` воспроизводимо создаёт и наполняет `demo.bookings.bookings` (>0 строк). +- `make bookings-init` (восстановление из дампа) и `make bookings-generate` (генерация с нуля) воспроизводимо создают и наполняют `demo.bookings.bookings` (>0 строк). - Если патчи не применяются — процесс останавливается с понятным сообщением, что делать дальше. - Патчи соответствуют закреплённому коммиту demodb и применяются идемпотентно. @@ -37,7 +39,7 @@ 1) Чистое воспроизведение: - `make clean` - `rm -rf bookings/demodb` -- `make bookings-init` +- `make bookings-generate` 2) Проверка данных: - `make bookings-psql` @@ -67,12 +69,12 @@ - было: `DROP DATABASE IF EXISTS demo;` - стало: `DROP DATABASE IF EXISTS demo WITH (FORCE);` -### Шаг 2. Сделать `make bookings-init` fail-fast на проблемах с патчами +### Шаг 2. Сделать `make bookings-generate` fail-fast на проблемах с патчами В `Makefile`: - убрать `|| true` у применения патчей; - при ошибке патча — завершать `make` с ненулевым кодом и короткой подсказкой: - - “удалите `bookings/demodb` и повторите `make bookings-init`”, + - “удалите `bookings/demodb` и повторите `make bookings-generate`”, - “если не помогло — проверьте, что `DEMODB_COMMIT` не менялся и патчи собраны под него”. ### Шаг 3. Добавить “защиту от тихого пустого результата” @@ -85,8 +87,8 @@ ## Проверка (критерии готовности) -- `make clean && rm -rf bookings/demodb && make bookings-init` завершается без ошибок. -- `make bookings-psql` → `SELECT COUNT(*) FROM bookings.bookings;` возвращает `> 0`. +- `make clean && rm -rf bookings/demodb && make bookings-generate` завершается без ошибок. +- `make bookings-psql` → `SELECT COUNT(*) FROM bookings.bookings;` возвращает `> 0` (для обоих способов: `bookings-init` и `bookings-generate`). - `make bookings-generate-day` добавляет следующий день: - `max(book_date)` сдвигается на +1 сутки. - `./scripts/e2e_smoke.sh` проходит до проверки `stg.bookings` (или хотя бы DAG `bookings_to_gp_stage` перестаёт падать на “источник пустой”). diff --git a/sql/src/bookings_generate_day_if_missing.sql b/sql/src/bookings_generate_day_if_missing.sql index f493cbc..58e03b3 100644 --- a/sql/src/bookings_generate_day_if_missing.sql +++ b/sql/src/bookings_generate_day_if_missing.sql @@ -14,12 +14,11 @@ DECLARE BEGIN -- Проверяем, что демобаза установлена IF to_regclass('bookings.bookings') IS NULL THEN - RAISE EXCEPTION 'Таблица bookings.bookings не найдена. Сначала выполните make bookings-init.'; + RAISE EXCEPTION 'Таблица bookings.bookings не найдена. Сначала выполните make bookings-init или make bookings-generate.'; END IF; - -- В учебном стенде поддерживается только jobs=1, иначе генерация нестабильна. - IF v_jobs <> 1 THEN - RAISE EXCEPTION 'Поддерживается только bookings.jobs=1. Текущее значение: %. Установите BOOKINGS_JOBS=1 и выполните make bookings-init.', v_jobs; + IF v_jobs < 1 THEN + RAISE EXCEPTION 'bookings.jobs должен быть >= 1. Текущее значение: %.', v_jobs; END IF; -- Ищем последнюю сгенерированную дату @@ -42,10 +41,19 @@ BEGIN CALL continue(v_end_date, v_jobs); END IF; + -- continue() делает TRUNCATE gen.stat_jobs → AccessExclusiveLock. + -- Без COMMIT воркеры не могут INSERT INTO gen.stat_jobs → deadlock. + COMMIT; + RAISE NOTICE 'Сгенерированы данные в bookings.bookings за интервал [% - %).', date_trunc('day', v_start_date), date_trunc('day', v_end_date); + -- При jobs>1 воркерам нужно время, чтобы подключиться через dblink + IF v_jobs > 1 THEN + PERFORM pg_sleep(3); + END IF; + -- Ждём завершения фоновых джобов генератора, чтобы данные успели записаться WHILE busy() LOOP PERFORM pg_sleep(1);