perf(bookings): BOOKINGS_JOBS=1 по умолчанию (3× быстрее на инкрементах)
- Зачем:
- бенчмарк на i7-11800H / WSL2 показал, что jobs=1 генерирует +1 день за ~3 мин,
а jobs=2 — за ~9 мин из-за lock contention на gen.events FOR UPDATE SKIP LOCKED.
Тюнинг PG (shared_buffers, work_mem) и synchronous_commit=off не влияют.
- Что:
- дефолт BOOKINGS_JOBS изменён с 2 на 1 в Makefile и .env.example.
- добавлен docs/internal/bookings_generation_benchmark.md с полной матрицей экспериментов.
- обновлены комментарии и bookings/README.md.
- Проверка:
- make bookings-init && time make bookings-generate-day (~3 мин).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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 не требуется для текущих объёмов данных.
|
||||
Reference in New Issue
Block a user