diff --git a/.env.example b/.env.example index a7a5172..1ad785e 100644 --- a/.env.example +++ b/.env.example @@ -24,8 +24,10 @@ BOOKINGS_DB_PORT=5434 BOOKINGS_START_DATE=2017-01-01 # Количество дней для генерации с нуля (make bookings-generate); для make bookings-init не используется BOOKINGS_INIT_DAYS=60 -# Количество параллельных джобов генератора (1 = синхронно без dblink, 2+ = через dblink) -BOOKINGS_JOBS=2 +# Количество параллельных джобов генератора (1 = синхронно без dblink, 2+ = через dblink). +# jobs=1 оптимален для инкрементальной генерации (+1 день ≈ 3 мин). +# jobs>1 на WSL2 в 3× медленнее из-за lock contention на gen.events. +BOOKINGS_JOBS=1 # Greenplum Configuration GP_USER=gpadmin diff --git a/Makefile b/Makefile index 845a980..bd671c1 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ UV := uv PYTHON_VERSION := 3.11 DEMODB_REPO := https://github.com/postgrespro/demodb.git DEMODB_COMMIT := 866e56f7fe54596a1d2a88f5f32f4aa3b2698121 -BOOKINGS_JOBS ?= 2 +BOOKINGS_JOBS ?= 1 BOOKINGS_START_DATE ?= 2017-01-01 BOOKINGS_INIT_DAYS ?= 60 diff --git a/bookings/README.md b/bookings/README.md index dd8884b..d45e4e2 100644 --- a/bookings/README.md +++ b/bookings/README.md @@ -26,7 +26,7 @@ - `busy()` игнорирует свой `pid`, чтобы не считать собственное подключение занятым; - `continue()` при `jobs=1` вызывает `process_queue` синхронно (без `dblink`), иначе генерация обрывается при выходе из `psql` и данных не появляется. - `install.sql`: удалён хардкод `gen.connstr` без credentials (`install_connstr_no_hardcode.patch`). -- Дефолт: `BOOKINGS_JOBS=2`. При `jobs=1` генерация синхронная (без dblink), при `jobs>1` — через dblink. +- Дефолт: `BOOKINGS_JOBS=1` (синхронно, без dblink — оптимально для +1 дня, ~3 мин). При `jobs>1` — через dblink, но на WSL2 в 3× медленнее из-за lock contention на `gen.events`. - Патчи применяются автоматически в `make bookings-generate` (генерация с нуля). Если что-то пошло не так, их можно накатить вручную: ``` patch -d bookings/demodb -p1 --forward < bookings/patches/install_drop_if_exists.patch diff --git a/docs/internal/bookings_generation_benchmark.md b/docs/internal/bookings_generation_benchmark.md new file mode 100644 index 0000000..e5bbd3d --- /dev/null +++ b/docs/internal/bookings_generation_benchmark.md @@ -0,0 +1,75 @@ +# Бенчмарк генерации bookings: параллельность и тюнинг PostgreSQL + +> Дата: 2026-03-09 +> Железо: Intel Core i7-11800H @ 2.30GHz (8 ядер / 16 потоков), игровой ноутбук +> Среда: WSL2, Docker Desktop, PostgreSQL 16 в контейнере +> Данные: seed-дамп 60 дней (~500k bookings, ~180k boarding_passes, ~1.2M tickets) + +## Контекст + +Генератор demodb (`postgrespro/demodb`) использует очередь событий `gen.events` с обработкой через `process_queue()`. При `jobs>1` воркеры запускаются через `dblink_send_query` и конкурируют за очередь через `SELECT ... FOR UPDATE SKIP LOCKED`. + +Задача: найти оптимальную конфигурацию для `make bookings-generate-day` (инкремент +1 день). + +## Методика + +- Между экспериментами: `make bookings-init BOOKINGS_JOBS=N` (восстановление из seed-дампа, ~18 сек). +- Замер: `time make bookings-generate-day BOOKINGS_JOBS=N`. +- Контроль: `max(book_date)` должен сдвинуться на +1 день. +- Тюнинг PG: `ALTER SYSTEM SET` + `docker compose restart bookings-db`. + +Параметры тюнинга PostgreSQL: +``` +shared_buffers = 512MB (дефолт: 128MB) +work_mem = 64MB (дефолт: 4MB) +maintenance_work_mem = 256MB (дефолт: 64MB) +effective_cache_size = 1GB (дефолт: 4GB) +wal_buffers = 16MB (дефолт: -1, авто) +checkpoint_completion_target = 0.9 (дефолт: 0.9) +random_page_cost = 1.1 (дефолт: 4.0) +``` + +## Результаты + +| # | Тюнинг PG | sync_commit | Jobs | Время | vs baseline | +|---|-----------|-------------|------|--------|--------------| +| 1 | нет | on | 1 | 2m 48s | **baseline** | +| 2 | нет | on | 2 | 8m 36s | 3× хуже | +| 3 | да | on | 1 | 3m 01s | шум | +| 4 | да | on | 2 | 8m 42s | 3× хуже | +| 5 | да | off | 1 | 2m 50s | шум | +| 6 | да | off | 2 | 8m 39s | 3× хуже | + +Повторный инкремент (прогретый кэш): 5m 03s (jobs=2), не замерялся (jobs=1). + +## Выводы + +### 1. jobs=1 оптимален для инкрементов + +Для генерации +1 дня параллельность через dblink **контрпродуктивна**: +- Два воркера конкурируют за одну очередь `gen.events` через `FOR UPDATE SKIP LOCKED` — это row-level lock contention. +- Overhead: dblink-соединения, синхронизация через `gen.stat_jobs`, polling через `dblink_is_busy()`. +- На WSL2 дополнительно: виртуализированный I/O не масштабируется при параллельных записях. + +При генерации с нуля (`make bookings-generate`, десятки тысяч событий) jobs>1 **может** давать прирост, но не тестировалось в этом бенчмарке. + +### 2. Тюнинг PostgreSQL не влияет + +Увеличение shared_buffers в 4 раза, work_mem в 16 раз и т.д. не дало измеримого эффекта. Узкое место — не буферы и не I/O, а сама логика генератора: последовательная обработка событий с COMMIT после каждого. + +### 3. synchronous_commit=off не влияет + +Генератор делает COMMIT после каждого события (~сотни раз за день). Ожидалось, что `synchronous_commit=off` ускорит запись WAL. Эффект не обнаружен — вероятно, WAL-буферы и так справляются при одном потоке. + +### 4. VACUUM-ивенты — отдельная проблема + +Генератор вставляет в очередь событие `VACUUM` каждую неделю модельного времени. Обработчик запускает `VACUUM ANALYZE` всей базы через `dblink_exec`. При 500k+ строках это занимает минуты. Решение: `DELETE FROM gen.events WHERE type = 'VACUUM'` перед `continue()` в инкрементальных скриптах. + +## Итоговая конфигурация + +``` +BOOKINGS_JOBS=1 # синхронно, без dblink — ~3 мин на +1 день +BOOKINGS_INIT_DAYS=60 # seed-дамп покрывает ~34 дня бронирований + boarding_passes +``` + +Тюнинг PostgreSQL не требуется для текущих объёмов данных.