Database Setup
Установка PostgreSQL и Redis на VPS.
1. PostgreSQL
- Dokploy UI → Projects → проект goLoot
- New Service → Database → PostgreSQL
- Настройки:
| Параметр | Значение |
|---|---|
| Name | postgresql |
| Docker image | postgres:18 (default) или postgres:16 |
| Database Name | goloot |
| Database User | goloot |
| Password | (см. ниже) |
| Версия | EOL | Рекомендация |
|---|---|---|
| 16 | Nov 2028 | Проверена с Dokploy, стабильная |
| 18 | Nov 2030 | Default в Dokploy, рекомендуется для новых установок |
Dokploy по умолчанию предлагает postgres:18. Для нашего проекта разница между версиями минимальна.
Сгенерируй надёжный пароль:
openssl rand -hex 24
Эта команда выдаст 48-символьный пароль из безопасных символов (0-9, a-f). Никаких спецсимволов, которые могут сломать connection string или shell.
Скопируй сгенерированный пароль и сохрани — он понадобится для connection string во всех сервисах.
Dokploy позволяет указать Command в настройках PostgreSQL. Не заполняй его при создании — это заменит entrypoint контейнера и PostgreSQL откажется запускаться с ошибкой "root" execution of the PostgreSQL server is not permitted.
Оптимизацию можно добавить после успешного запуска — см. секцию ниже.
- Deploy
Dokploy создаст контейнер PostgreSQL в сети dokploy-network.
Connection string (для других сервисов в Dokploy):
postgresql://goloot:YOUR_PASSWORD@DOKPLOY_SERVICE_NAME:5432/goloot
Внутри 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 почти бессмысленны.
- PostgreSQL → вкладка Advanced → прокрутить вниз до секции Resources
- Изменить значения:
| Параметр | По умолчанию | Рекомендация | Значение в bytes |
|---|---|---|---|
| Memory Limit | 1 GB | 4 GB | 4294967296 |
| Memory Reservation | 256 MB | 512 MB | 536870912 |
| CPU Limit | 2 CPUs | Оставить | — |
| CPU Reservation | 1 CPU | Оставить | — |
- Memory Limit — потолок RAM для контейнера. Нужно увеличить, чтобы PostgreSQL мог использовать больше кэша.
- Memory Reservation — минимум гарантированной памяти. 512 MB достаточно, чтобы PostgreSQL не "отжимали" другие контейнеры при нехватке RAM.
- CPU — PostgreSQL в основном упирается в диск (I/O), а не в CPU. Дефолтные значения достаточны.
- Нажать Save, затем Redeploy
Шаг 2: Настроить Command (параметры PostgreSQL)
Параметры PostgreSQL задаются через вкладку Advanced → Command.
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_buffers | 1 GB | Кэш данных в RAM (~25% от Memory Limit) |
effective_cache_size | 3 GB | Оценка доступного кэша (~75% от Memory Limit) |
work_mem | 16 MB | Память для сложных запросов |
maintenance_work_mem | 256 MB | Память для VACUUM, CREATE INDEX |
max_connections | 200 | Макс. подключений (дефолт 100 — мало при pool=50) |
Dokploy Redeploy не пересоздаёт контейнер для database сервисов — старый контейнер продолжает работать со старой командой. Для применения изменений в Command:
- Save → Stop → дождаться полной остановки → Start
- Проверить:
docker exec <container> psql -U goloot -c "SHOW max_connections;"
Альтернатива из CLI: docker service update --force $(docker service ls -q -f name=goloot-postgres)
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, затем Stop → Start (не Redeploy — см. предупреждение выше).
2. Redis (опционально)
Redis используется как cache-aside layer для ускорения горячих запросов (сезоны, квесты, кейсы, рулетки, лидерборд) и трекинга invite-ссылок к документации. Если Redis недоступен — backend продолжает работать (запросы идут напрямую в PostgreSQL).
Архитектура кэширования, TTL-стратегии и Prometheus-метрики описаны в ADR: Redis Integration и Caching Strategy.
- Dokploy UI → проект goLoot
- New Service → Database → Redis
- Настройки:
| Параметр | Значение |
|---|---|
| Name | redis |
| Version | 7 |
| Password | (сгенерируй через openssl rand -hex 24) |
- 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.
Настройка подключения
- Открыть Beekeeper Studio → New Connection → Postgres
- Включить SSH Tunnel (переключатель внизу формы)
Connection:
| Поле | Значение | Описание |
|---|---|---|
| Host | localhost | Через SSH-тоннель — всегда localhost |
| Port | 5432 | Стандартный порт PostgreSQL |
| User | goloot | Пользователь БД (из настроек PostgreSQL) |
| Password | YOUR_PASSWORD | Пароль PostgreSQL |
| Default Database | goloot | Имя базы данных |
SSH Tunnel:
| Поле | Значение | Описание |
|---|---|---|
| SSH Hostname | YOUR_VPS_IP | IP-адрес сервера |
| SSH Port | 22 | Стандартный SSH-порт |
| SSH Username | root | Пользователь SSH |
| Private Key File | Путь к SSH-ключу | Например: C:\Users\Name\.ssh\id_ed25519 (Windows) |
Тот же ключ, который используется для SSH-подключения к серверу (см. Server Setup). На Windows обычно C:\Users\{Name}\.ssh\id_ed25519 или id_rsa.
- Нажать Test → убедиться что подключение работает
- Save → подключение сохранится для будущих сессий
В поле Host указывай localhost, а не внешний IP сервера. SSH-тоннель сам пробросит соединение на VPS. Порт 5432 закрыт UFW — прямое подключение по внешнему IP не пройдёт.
5. Инициализация базы данных
Сначала создай и задеплой Backend (см. Services Deployment), затем вернись сюда.
Применить Prisma-схему
PostgreSQL в Dokploy доступен только внутри Docker-сети — порт не открыт наружу. Есть два способа применить миграции.
| Команда | Когда | Что делает |
|---|---|---|
db push | Dev/staging, активная разработка | Синхронизирует схему напрямую, может потерять данные |
migrate deploy | Production | Применяет только созданные миграции, безопасно |
В Dokploy у PostgreSQL два connection URL:
| URL | Пример host | Когда использовать |
|---|---|---|
| Internal | goloot-postgres-j0kaqc | Контейнер → контейнер (Backend env, Docker Terminal) |
| External | localhost через 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-тоннель — используй External (с localhost). Если из 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
DATABASE_URL должен указывать на localhost:5432 (через тоннель), а не на внешний IP сервера. Порт 5432 закрыт UFW — подключение по внешнему IP не пройдёт.
Способ B: Через Dokploy Docker Terminal
Миграции уже находятся внутри Backend контейнера (попадают туда при сборке Docker-образа). Можно выполнить их напрямую.
- Dokploy UI → Backend → вкладка Deployments или Advanced → Docker Terminal
- Выбрать running контейнер → /bin/sh
- Внутри контейнера:
npx prisma migrate deploy # production
npx prisma db push # dev/staging
- Когда нет возможности открыть SSH-тоннель
- Для быстрой проверки статуса:
npx prisma migrate status prismaCLI скачается при первом запуске через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 | Описание |
|---|---|---|
| 1 | seed-case-types | Типы кейсов (FK-зависимость) |
| 2 | seed-spin-types | Типы рулеток (FK-зависимость) |
| 3 | seed-resources | Игровые ресурсы для крафта скинов |
| 4 | seed-buffs | Баффы |
Можно запустить напрямую: npx tsx prisma/seed/index.ts
6. Бэкап PostgreSQL
Dokploy имеет встроенную систему бэкапов PostgreSQL прямо в S3-совместимое хранилище — без необходимости настраивать cron или bash-скрипты вручную.
Настройка
- Настрой S3 Destination: Dokploy UI → Settings → S3 Destinations → + Add Destination
- Provider →
Any other S3 compatible provider(подходит для Backblaze B2, Wasabi, MinIO и любого S3-совместимого хранилища; для AWS S3 выбериAWS S3) - Заполни Access Key Id, Secret Access Key, Bucket, Region, Endpoint
- Provider →
- Dokploy UI → проект goLoot → сервис PostgreSQL → вкладка Backups
- Выбери S3 Destination и настрой расписание (например,
0 3 * * *— ежедневно в 03:00)
Dokploy автоматически выполняет pg_dump, сжимает и загружает в S3.
Восстановление
Dokploy UI → PostgreSQL → Backups → выбрать бэкап → Restore
Подробнее о бэкапах, восстановлении и миграции: Backup & Migration
7. Connection Pool & Scaling
Текущая конфигурация
| Параметр | Значение | Где задаётся |
|---|---|---|
max_connections | 200 | PostgreSQL Command (-c max_connections=200) |
Backend connection_limit | 50 | DATABASE_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 коннектов
При деплое 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 VU | 500 VU | 1000 VU |
|---|---|---|---|
| median | 85ms | 200ms | 350ms |
| p95 | 1.01s | 719ms | 26s |
| 5xx rate | 0.68% | 0.34% | 30.8% |
| RPS | 52/s | 319/s | 216/s |
Между тестами был применён кэш 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 |
|---|---|---|---|---|
| Idle | 3-4% | 2-3% | 6-9% | 80-88% |
| Sustained load | 22-30% | 7-10% | 15-25% | 44-55% |
| Peak (ramp-up burst) | 76% | 18% | 24% | 3% |
Ключевые выводы:
-
Ни CPU, ни pool, ни RAM не являются bottleneck. При 500 VU (319 req/s): CPU sustained ~32%, RAM +0.2GB, pool не насыщался (увеличение 25→50 не изменило p95).
-
p95 определяется сетью между k6 и сервером. k6 запускался через домашний интернет (~1 Мбит upload). При 500+ VU TCP congestion на канале k6-машины вызывает retransmits и задержки. Реальные пользователи идут каждый через свой канал — этой проблемы нет.
-
%steal ~18% — VPS на shared хостинге. Гипервизор забирает ~18% CPU у других арендаторов. При необходимости — переход на dedicated CPU (Hetzner CCX) даст %steal = 0%.
-
1000 VU — сломался k6, не сервер. 30% ошибок при 1000 VU — это TCP timeouts на стороне k6 (домашний канал не вытянул). CPU на сервере при этом был 25-38% user, далеко от потолка.
-
Для 5000 DAU (20-50 concurrent) — запас в 10+ раз. Текущая конфигурация более чем достаточна.
Когда масштабировать
| Признак | Действие |
|---|---|
| %steal > 30% sustained | Переход на dedicated CPU VPS (Hetzner CCX) |
| CPU %user > 60% sustained | Upgrade 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,000 | PgBouncer + horizontal scaling |
# Запустить ДО нагрузочного теста (каждые 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.
Related
- Dokploy Installation — предыдущий шаг
- Services Deployment — следующий шаг
- Environment Variables — полный список переменных
- Backup & Migration — стратегии бэкапов