Масштабирование

Масштабирование

Раздел описывает переход от 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 сразу после переноса — оставьте как резервную копию до подтверждения корректной работы с внешней БД.

Связанные разделы