Skip to main content

Database Setup

Установка PostgreSQL и Redis на VPS.


1. PostgreSQL

  1. Dokploy UI → Projects → проект goLoot
  2. New ServiceDatabasePostgreSQL
  3. Настройки:
ПараметрЗначение
Namepostgresql
Docker imagepostgres:18 (default) или postgres:16
Database Namegoloot
Database Usergoloot
Password(см. ниже)
Какую версию выбрать?
ВерсияEOLРекомендация
16Nov 2028Проверена с Dokploy, стабильная
18Nov 2030Default в Dokploy, рекомендуется для новых установок

Dokploy по умолчанию предлагает postgres:18. Для нашего проекта разница между версиями минимальна.

Сгенерируй надёжный пароль:

openssl rand -hex 24

Эта команда выдаст 48-символьный пароль из безопасных символов (0-9, a-f). Никаких спецсимволов, которые могут сломать connection string или shell.

Сохрани пароль!

Скопируй сгенерированный пароль и сохрани — он понадобится для connection string во всех сервисах.

Поле Command должно быть ПУСТЫМ при создании!

Dokploy позволяет указать Command в настройках PostgreSQL. Не заполняй его при создании — это заменит entrypoint контейнера и PostgreSQL откажется запускаться с ошибкой "root" execution of the PostgreSQL server is not permitted.

Оптимизацию можно добавить после успешного запуска — см. секцию ниже.

  1. Deploy

Dokploy создаст контейнер PostgreSQL в сети dokploy-network.

Connection string (для других сервисов в Dokploy):

postgresql://goloot:YOUR_PASSWORD@DOKPLOY_SERVICE_NAME:5432/goloot
Внутренний DNS

Внутри Docker-сети dokploy-network сервисы обращаются друг к другу по имени сервиса. Dokploy генерирует уникальное имя при создании (например, goloot-postgres-de09zz). Узнать имя можно через:

docker service ls | grep postgres

Это имя используется как hostname в connection strings всех сервисов.

Оптимизация PostgreSQL (опционально, не нужно на старте)

По умолчанию PostgreSQL использует консервативные настройки (128 MB кэша вместо возможных 2 GB). На старте проекта это не проблема — разница заметна только при высокой нагрузке (тысячи пользователей, тяжёлые запросы).

Оптимизация состоит из двух частей: Resources (лимиты контейнера) и Command (настройки PostgreSQL).

Шаг 1: Увеличить Resources (лимиты контейнера)

Dokploy по умолчанию ставит Memory Limit = 1 GB для PostgreSQL. При таком лимите оптимизации через Command почти бессмысленны.

  1. PostgreSQL → вкладка Advanced → прокрутить вниз до секции Resources
  2. Изменить значения:
ПараметрПо умолчаниюРекомендацияЗначение в bytes
Memory Limit1 GB4 GB4294967296
Memory Reservation256 MB512 MB536870912
CPU Limit2 CPUsОставить
CPU Reservation1 CPUОставить
Почему только Memory?
  • Memory Limit — потолок RAM для контейнера. Нужно увеличить, чтобы PostgreSQL мог использовать больше кэша.
  • Memory Reservation — минимум гарантированной памяти. 512 MB достаточно, чтобы PostgreSQL не "отжимали" другие контейнеры при нехватке RAM.
  • CPU — PostgreSQL в основном упирается в диск (I/O), а не в CPU. Дефолтные значения достаточны.
  1. Нажать Save, затем Redeploy

Шаг 2: Настроить Command (параметры PostgreSQL)

Параметры PostgreSQL задаются через вкладку AdvancedCommand.

Обязательно с docker-entrypoint.sh!

Command должен начинаться с docker-entrypoint.sh — это сохраняет штатный entrypoint, который переключает процесс с root на пользователя postgres. Без него PostgreSQL откажется запускаться.

docker-entrypoint.sh postgres -c shared_buffers=1GB -c effective_cache_size=3GB -c work_mem=16MB -c maintenance_work_mem=256MB -c max_connections=200

Значения рассчитаны для Memory Limit контейнера = 4 GB. Для точного расчёта используй PgTune.

ПараметрЗначениеОписание
shared_buffers1 GBКэш данных в RAM (~25% от Memory Limit)
effective_cache_size3 GBОценка доступного кэша (~75% от Memory Limit)
work_mem16 MBПамять для сложных запросов
maintenance_work_mem256 MBПамять для VACUUM, CREATE INDEX
max_connections200Макс. подключений (дефолт 100 — мало при pool=50)
Command изменения требуют Stop → Start!

Dokploy Redeploy не пересоздаёт контейнер для database сервисов — старый контейнер продолжает работать со старой командой. Для применения изменений в Command:

  1. SaveStop → дождаться полной остановки → Start
  2. Проверить: docker exec <container> psql -U goloot -c "SHOW max_connections;"

Альтернатива из CLI: docker service update --force $(docker service ls -q -f name=goloot-postgres)

Command привязан к Memory Limit!

shared_buffers не может превышать Memory Limit контейнера — иначе PostgreSQL не запустится. Если меняешь Memory Limit — пересчитай Command.

Если оставляешь Memory Limit = 1 GB (без шага 1) — используй уменьшенные значения:

docker-entrypoint.sh postgres -c shared_buffers=256MB -c effective_cache_size=768MB -c work_mem=8MB -c maintenance_work_mem=128MB -c max_connections=200

Выигрыш при 1 GB лимите минимален. Реальный эффект — при лимите 4+ GB.

Нажать Save, затем StopStart (не Redeploy — см. предупреждение выше).


2. Redis (опционально)

Redis используется как cache-aside layer для ускорения горячих запросов (сезоны, квесты, кейсы, рулетки, лидерборд) и трекинга invite-ссылок к документации. Если Redis недоступен — backend продолжает работать (запросы идут напрямую в PostgreSQL).

Подробнее о кэшировании

Архитектура кэширования, TTL-стратегии и Prometheus-метрики описаны в ADR: Redis Integration и Caching Strategy.

  1. Dokploy UI → проект goLoot
  2. New ServiceDatabaseRedis
  3. Настройки:
ПараметрЗначение
Nameredis
Version7
Password(сгенерируй через openssl rand -hex 24)
  1. Deploy

Connection string:

redis://:YOUR_PASSWORD@redis:6379

3. Проверка подключений

PostgreSQL

# Из контейнера PostgreSQL:
docker exec -it $(docker ps -q -f name=postgresql) psql -U goloot -d goloot -c "SELECT 1"

# Или с хоста — сначала узнай точное имя контейнера, затем подключись:
docker ps --format "{{.Names}}" | grep postgres
docker exec -it CONTAINER_NAME psql -U goloot -d goloot -c "SELECT 1"

Если видишь 1 — подключение работает.

Redis

# Из контейнера Redis (используй goloot-redis, т.к. Dokploy имеет свой внутренний Redis):
docker exec -it $(docker ps -q -f name=goloot-redis) redis-cli -a YOUR_REDIS_PASSWORD ping

# Или с хоста:
docker ps --format "{{.Names}}" | grep redis
docker exec -it CONTAINER_NAME redis-cli -a YOUR_REDIS_PASSWORD ping
# Ответ: PONG

4. Подключение через GUI (Beekeeper Studio)

PostgreSQL в Dokploy доступен только внутри Docker-сети — порт 5432 закрыт UFW. Для подключения через GUI-клиент используй SSH Tunnel.

Настройка подключения

  1. Открыть Beekeeper Studio → New ConnectionPostgres
  2. Включить SSH Tunnel (переключатель внизу формы)

Connection:

ПолеЗначениеОписание
HostlocalhostЧерез SSH-тоннель — всегда localhost
Port5432Стандартный порт PostgreSQL
UsergolootПользователь БД (из настроек PostgreSQL)
PasswordYOUR_PASSWORDПароль PostgreSQL
Default DatabasegolootИмя базы данных

SSH Tunnel:

ПолеЗначениеОписание
SSH HostnameYOUR_VPS_IPIP-адрес сервера
SSH Port22Стандартный SSH-порт
SSH UsernamerootПользователь SSH
Private Key FileПуть к SSH-ключуНапример: C:\Users\Name\.ssh\id_ed25519 (Windows)
Где взять SSH-ключ?

Тот же ключ, который используется для SSH-подключения к серверу (см. Server Setup). На Windows обычно C:\Users\{Name}\.ssh\id_ed25519 или id_rsa.

  1. Нажать Test → убедиться что подключение работает
  2. Save → подключение сохранится для будущих сессий
Не используй внешний IP в Host!

В поле Host указывай localhost, а не внешний IP сервера. SSH-тоннель сам пробросит соединение на VPS. Порт 5432 закрыт UFW — прямое подключение по внешнему IP не пройдёт.


5. Инициализация базы данных

Это делается после деплоя Backend

Сначала создай и задеплой Backend (см. Services Deployment), затем вернись сюда.

Применить Prisma-схему

PostgreSQL в Dokploy доступен только внутри Docker-сети — порт не открыт наружу. Есть два способа применить миграции.

Когда что использовать
КомандаКогдаЧто делает
db pushDev/staging, активная разработкаСинхронизирует схему напрямую, может потерять данные
migrate deployProductionПрименяет только созданные миграции, безопасно
Internal vs External URL — когда какой

В Dokploy у PostgreSQL два connection URL:

URLПример hostКогда использовать
Internalgoloot-postgres-j0kaqcКонтейнер → контейнер (Backend env, Docker Terminal)
Externallocalhost через SSH-туннельЛокальная машина → БД через SSH-тоннель

Internal URL — работает только внутри Docker-сети (dokploy-network). Используется в DATABASE_URL контейнера Backend, а также при выполнении команд через Docker Terminal.

External URL — для подключения извне. Порт 5432 закрыт UFW, поэтому подключение идёт через SSH-тоннель (ssh -L 5432:localhost:5432). В DATABASE_URL указывай localhost:5432, не внешний IP.

Если применяешь миграции с локальной машины через SSH-тоннель — используй Externallocalhost). Если из Docker Terminal контейнера — Internal URL уже настроен в env.

Способ A: SSH-тоннель с локальной машины (рекомендуется)

Позволяет запускать Prisma команды с локальной машины через SSH-тоннель к VPS.

Терминал 1 — открыть тоннель:

ssh -L 5432:localhost:5432 root@YOUR_VPS_IP
# Оставить открытым на время работы

Терминал 2 — применить миграции:

cd backend

# DATABASE_URL указывает на localhost через тоннель:
DATABASE_URL="postgresql://goloot:YOUR_PASSWORD@localhost:5432/goloot" npx prisma migrate deploy

# Или для dev/staging:
DATABASE_URL="postgresql://goloot:YOUR_PASSWORD@localhost:5432/goloot" npx prisma db push
Не используй внешний IP!

DATABASE_URL должен указывать на localhost:5432 (через тоннель), а не на внешний IP сервера. Порт 5432 закрыт UFW — подключение по внешнему IP не пройдёт.

Способ B: Через Dokploy Docker Terminal

Миграции уже находятся внутри Backend контейнера (попадают туда при сборке Docker-образа). Можно выполнить их напрямую.

  1. Dokploy UI → Backend → вкладка Deployments или AdvancedDocker Terminal
  2. Выбрать running контейнер → /bin/sh
  3. Внутри контейнера:
npx prisma migrate deploy   # production
npx prisma db push # dev/staging
Когда использовать Docker Terminal
  • Когда нет возможности открыть SSH-тоннель
  • Для быстрой проверки статуса: npx prisma migrate status
  • prisma CLI скачается при первом запуске через npx (не установлен в production-образе)

Загрузить начальные данные (Seeds)

Seed-скрипты заполняют справочники (типы кейсов, рулеток, ресурсы, баффы). Все seeds объединены в единый entry point prisma/seed/index.ts.

Способ A: С локальной машины через SSH-тоннель:

# Терминал 1: SSH-тоннель уже открыт
# Терминал 2:
cd backend

DATABASE_URL="postgresql://goloot:YOUR_PASSWORD@localhost:5432/goloot" npx prisma db seed

Способ B: Через Dokploy Docker Terminal:

# Внутри контейнера backend:
npx prisma db seed

Команда запустит все seeds по порядку: CaseTypes → SpinTypes → Resources → Buffs.

Что внутри seed (содержимое prisma/seed/index.ts)
#SeedОписание
1seed-case-typesТипы кейсов (FK-зависимость)
2seed-spin-typesТипы рулеток (FK-зависимость)
3seed-resourcesИгровые ресурсы для крафта скинов
4seed-buffsБаффы

Можно запустить напрямую: npx tsx prisma/seed/index.ts


6. Бэкап PostgreSQL

Dokploy имеет встроенную систему бэкапов PostgreSQL прямо в S3-совместимое хранилище — без необходимости настраивать cron или bash-скрипты вручную.

Настройка

  1. Настрой S3 Destination: Dokploy UI → SettingsS3 Destinations+ Add Destination
    • ProviderAny other S3 compatible provider (подходит для Backblaze B2, Wasabi, MinIO и любого S3-совместимого хранилища; для AWS S3 выбери AWS S3)
    • Заполни Access Key Id, Secret Access Key, Bucket, Region, Endpoint
  2. Dokploy UI → проект goLoot → сервис PostgreSQL → вкладка Backups
  3. Выбери S3 Destination и настрой расписание (например, 0 3 * * * — ежедневно в 03:00)

Dokploy автоматически выполняет pg_dump, сжимает и загружает в S3.

Восстановление

Dokploy UI → PostgreSQL → Backups → выбрать бэкап → Restore

Подробнее о бэкапах, восстановлении и миграции: Backup & Migration


7. Connection Pool & Scaling

Текущая конфигурация

ПараметрЗначениеГде задаётся
max_connections200PostgreSQL Command (-c max_connections=200)
Backend connection_limit50DATABASE_URL?connection_limit=50 в Dokploy env
Redirect connection_limit~5Дефолт Prisma (не задан явно)

Распределение коннектов

PostgreSQL max_connections = 200
├── superuser_reserved = 3
├── Доступно = 197

├── Backend: 50 (connection_limit в URL)
├── Backend при деплое: 100 (overlap: старый + новый контейнер)
├── Redirect Service: 5 (дефолт Prisma)
├── psql / backup: 5 (ручные подключения)

├── Peak (деплой): ~112 из 197 (57%)
└── Запас: 85 коннектов
Не ставь connection_limit > 80

При деплое Dokploy на ~30-60 сек работают два backend контейнера — каждый открывает свой пул. При connection_limit=80 и деплое: 80 × 2 + 5 = 165 — остаётся 32 коннекта. При одновременном backup или ручном psql — может не хватить.

Формула: connection_limit ≤ (max_connections - reserve) / 2, где reserve = redirect + psql + backup ≈ 15.

Результаты нагрузочного тестирования (2026-03-22)

Сервер: 6 vCPU (Intel Broadwell), 11GB RAM, Hetzner VPS.

Тест: k6 с локальной машины (WSL) → api.goloot.online, case open endpoint.

Метрика200 VU500 VU1000 VU
median85ms200ms350ms
p951.01s719ms26s
5xx rate0.68%0.34%30.8%
RPS52/s319/s216/s
Почему 500 VU лучше 200 VU?

Между тестами был применён кэш pre-transaction запросов (ISS-010) — Redis кэширование budget/boost/override убрало ~5 DB round-trips с каждого case open. p95 упал с 1.01s до 719ms.

CPU на сервере при 500 VU (данные sar -u 2):

Фаза%user%system%steal%idle
Idle3-4%2-3%6-9%80-88%
Sustained load22-30%7-10%15-25%44-55%
Peak (ramp-up burst)76%18%24%3%

Ключевые выводы:

  1. Ни CPU, ни pool, ни RAM не являются bottleneck. При 500 VU (319 req/s): CPU sustained ~32%, RAM +0.2GB, pool не насыщался (увеличение 25→50 не изменило p95).

  2. p95 определяется сетью между k6 и сервером. k6 запускался через домашний интернет (~1 Мбит upload). При 500+ VU TCP congestion на канале k6-машины вызывает retransmits и задержки. Реальные пользователи идут каждый через свой канал — этой проблемы нет.

  3. %steal ~18% — VPS на shared хостинге. Гипервизор забирает ~18% CPU у других арендаторов. При необходимости — переход на dedicated CPU (Hetzner CCX) даст %steal = 0%.

  4. 1000 VU — сломался k6, не сервер. 30% ошибок при 1000 VU — это TCP timeouts на стороне k6 (домашний канал не вытянул). CPU на сервере при этом был 25-38% user, далеко от потолка.

  5. Для 5000 DAU (20-50 concurrent) — запас в 10+ раз. Текущая конфигурация более чем достаточна.

Когда масштабировать

ПризнакДействие
%steal > 30% sustainedПереход на dedicated CPU VPS (Hetzner CCX)
CPU %user > 60% sustainedUpgrade VPS CPU или horizontal scaling (2+ backend)
Pool utilization > 80% (Grafana alert)Увеличить connection_limit (до 80 max)
max_connections exhaustionУвеличить до 300-500 (+ RAM: каждый коннект ≈ 5-10MB)
DAU > 20,000PgBouncer + horizontal scaling
Как снять метрики CPU на сервере
# Запустить ДО нагрузочного теста (каждые 2 сек, 120 замеров):
sar -u 2 120 > /tmp/cpu_load.txt &
# После теста:
cat /tmp/cpu_load.txt

Ключевые колонки: %user (приложение), %steal (гипервизор), %idle (свободно).

k6 тесты

Стресс-тесты доступны в backend/tests/k6/. Подробности: backend/tests/k6/README.md.


8. Чеклист

  • PostgreSQL установлен и работает
  • База goloot и пользователь созданы
  • Redis установлен (опционально, рекомендуется для production)
  • Connection strings записаны (понадобятся для env vars)
  • Автоматический бэкап настроен

Записать и сохранить:

DATABASE_URL=postgresql://goloot:PASSWORD@DOKPLOY_SERVICE_NAME:5432/goloot
REDIS_URL=redis://:PASSWORD@DOKPLOY_REDIS_SERVICE_NAME:6379

Имена сервисов узнаёшь через docker service ls | grep -E "postgres|redis" на VPS.