fix(bookings): исправлена генерация demodb и добавлен seed-дамп
- Зачем:
- генерация с нуля занимала часы, boarding_passes были пустыми, данные
пропадали после docker compose down/up.
- Что:
- обновлён demodb до коммита 866e56f, добавлен патч install_connstr_no_hardcode.
- BOOKINGS_INIT_DAYS увеличен до 60, BOOKINGS_JOBS по умолчанию 2.
- добавлен seed-дамп bookings/seed/demo.sql.xz (42 MB, xz вместо 7z).
- make bookings-init восстанавливает из дампа (~18 сек) и применяет GUC из .env.
- make bookings-generate — генерация с нуля для разработчиков.
- generate_next_day.sql: COMMIT после continue(), pg_sleep(3) для jobs>1, synchronous_commit=on + CHECKPOINT.
- bookings-check-jobs: добавлена валидация нечисловых значений BOOKINGS_JOBS.
- bookings-init теперь вызывает bookings-check-jobs как prerequisite.
- синхронизированы внутренние документы (коммит demodb, init_days=60).
- Проверка:
- make bookings-init && make bookings-generate-day BOOKINGS_JOBS=2.
- make test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
-4
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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`), затем повторите запуск |
|
||||
|
||||
+2
-2
@@ -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).
|
||||
|
||||
@@ -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` валится на проверках (источник пустой).
|
||||
|
||||
+15
-6
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
Binary file not shown.
@@ -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 (нужен запуск стенда)
|
||||
|
||||
@@ -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 день по умолчанию, чтобы генерация не занимала много времени.
|
||||
|
||||
|
||||
+2
-2
@@ -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 (побочный пример)
|
||||
|
||||
|
||||
@@ -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) добавляется ровно один новый день.
|
||||
|
||||
---
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
## Контекст
|
||||
|
||||
В стенде используется демобаза bookings из репозитория `postgrespro/demodb`, закреплённая на коммите `d68de192850237719f09b47688d5f3fc94653ca6` (см. `DEMODB_COMMIT` в `Makefile`).
|
||||
В стенде используется демобаза bookings из репозитория `postgrespro/demodb`, закреплённая на коммите `866e56f7fe54596a1d2a88f5f32f4aa3b2698121` (см. `DEMODB_COMMIT` в `Makefile`).
|
||||
|
||||
Инициализация источника для ETL выполняется командой `make bookings-init`:
|
||||
Инициализация источника для ETL выполняется двумя способами:
|
||||
- `make bookings-init` — быстрое восстановление из seed-дампа (~18 сек, рекомендуется для студентов);
|
||||
- `make bookings-generate` — полная генерация с нуля (для разработчиков):
|
||||
- клонирует demodb в `bookings/demodb/`;
|
||||
- пытается применить патчи из `bookings/patches/`;
|
||||
- запускает `install.sql` в контейнере `bookings-db`;
|
||||
@@ -13,7 +15,7 @@
|
||||
|
||||
## Симптомы (как в 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` перестаёт падать на “источник пустой”).
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user