Масштабирование
Масштабирование
Раздел описывает переход от Standalone-режима (всё в одном контейнере) к горизонтальному масштабированию: отдельный PostgreSQL, PgBouncer и несколько инстансов приложения.
Для базовой настройки производительности в одном контейнере — см. Производительность.
Когда нужно масштабирование
- 80 одновременных запросов (8 workers × 10 threads) недостаточно
- Нужна высокая доступность (несколько инстансов)
- PostgreSQL и Rails конкурируют за ресурсы одного сервера
Архитектура
Клиенты
↓
nginx / Traefik (балансировщик)
↓
web-1, web-2, web-3 (инстансы RouterAI)
↓
PgBouncer (connection pooler)
↓
PostgreSQL (отдельный сервер)
Шаг 1: Поднять отдельный PostgreSQL
PostgreSQL запускается отдельно — не через compose приложения. Варианты:
Отдельный docker run:
docker run -d \
--name routerai-postgres \
--restart unless-stopped \
-e POSTGRES_USER=rails \
-e POSTGRES_PASSWORD=your-password \
-e POSTGRES_DB=routerai_prem_production \
-v /data/postgres:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:17
Managed PostgreSQL (Yandex Cloud, AWS RDS, и т.п.) — используйте предоставленный хост и учётные данные.
Шаг 2: Перенести данные из встроенного PostgreSQL
2.1 Создать бэкап текущей базы
Через интерфейс RouterAI: раздел Бэкапы → «Создать бэкап» → скачать файл дампа.
Или вручную (если приложение запущено через compose):
docker compose exec postgres pg_dump -U rails routerai_prem_production > backup.sql
Если приложение запущено через docker run (встроенный PostgreSQL):
docker exec <container_name> pg_dump -U rails routerai_prem_production > backup.sql
2.2 Восстановить дамп на внешнем PostgreSQL
psql -h your-postgres-host -U rails routerai_prem_production < backup.sql
Шаг 3: Настроить PgBouncer
PgBouncer мультиплексирует соединения: тысячи клиентских соединений → десятки реальных соединений к PostgreSQL.
Без PgBouncer при 3 инстансах: 3 × 8 × 11 = 264 соединения — PostgreSQL не выдержит.
С PgBouncer: те же 264 клиента → 50 реальных соединений к PG.
Запустите PgBouncer отдельно или добавьте в compose.override.yml:
services:
pgbouncer:
image: pgbouncer/pgbouncer:latest
restart: unless-stopped
environment:
- DATABASES_HOST=your-postgres-host
- DATABASES_PORT=5432
- DATABASES_DBNAME=routerai_prem_production
- DATABASES_USER=rails
- DATABASES_PASSWORD=your-password
- POOL_MODE=transaction
- MAX_CLIENT_CONN=1000
- DEFAULT_POOL_SIZE=50
ports:
- "5432:5432"
Шаг 4: Настроить инстансы приложения
Добавьте в compose.override.yml:
services:
web:
environment:
# Указываем на PgBouncer (или напрямую на PostgreSQL если без PgBouncer)
- DATABASE_HOST=pgbouncer
- DATABASE_NAME=routerai_prem_production
- DATABASE_USER=rails
- DATABASE_PASSWORD=your-password
- WEB_CONCURRENCY=8
- RAILS_MAX_THREADS=10
- DATABASE_POOL=11
# Solid Queue только в одном инстансе
- SKIP_SOLID_QUEUE=true
deploy:
replicas: 3
# Отдельный контейнер для Solid Queue (один на весь кластер)
queue_worker:
image: <same image as web>
command: bundle exec rails solid_queue:start
environment:
- RAILS_ENV=production
- DATABASE_HOST=pgbouncer
- DATABASE_NAME=routerai_prem_production
- DATABASE_USER=rails
- DATABASE_PASSWORD=your-password
restart: unless-stopped
Запустите:
docker compose up -d --scale web=3
Переменные окружения
| Переменная | Описание |
|---|---|
DATABASE_HOST |
Хост PostgreSQL или PgBouncer. Если не задан или localhost — запускается встроенный PostgreSQL |
DATABASE_NAME |
Имя базы данных |
DATABASE_USER |
Пользователь PostgreSQL |
DATABASE_PASSWORD |
Пароль PostgreSQL |
SKIP_SOLID_QUEUE |
true — не запускать Solid Queue в этом инстансе |
Расчёт соединений
| Инстансов | WEB_CONCURRENCY | DATABASE_POOL | Соединений к PgBouncer | Реальных к PG |
|---|---|---|---|---|
| 1 | 8 | 11 | 88 | 50 (через PgBouncer) |
| 3 | 8 | 11 | 264 | 50 (через PgBouncer) |
| 5 | 8 | 11 | 440 | 50 (через PgBouncer) |
Solid Queue при масштабировании
Solid Queue использует PostgreSQL как хранилище задач. При нескольких инстансах web Solid Queue должен работать только в одном контейнере — иначе задачи будут выполняться несколько раз.
Правило: установите SKIP_SOLID_QUEUE=true во всех инстансах web и запустите отдельный контейнер queue_worker.
Важные замечания
⚠️ Все инстансы
webдолжны использовать одинаковое значениеSECRET_KEY_BASE— иначе сессии пользователей будут инвалидироваться при переключении между инстансами.⚠️ PgBouncer в режиме
transactionнесовместим сLISTEN/NOTIFY. Solid Cable (WebSocket) используетLISTEN/NOTIFY— если используете Solid Cable, подключайте его напрямую к PostgreSQL, минуя PgBouncer.⚠️ Не удаляйте volume встроенного PostgreSQL сразу после переноса — оставьте как резервную копию до подтверждения корректной работы с внешней БД.