This commit captures the full server code accumulated across
several development sessions. It includes the following layers,
applied in roughly this order during development:
1. Base infrastructure
- cmd/poc-server HTTP+WebSocket server, /health, /stats
- internal/transport wire types (Envelope + all message types)
- internal/catalog, internal/realtime, internal/stats
- internal/storage/postgres with migrations 001-004
- .env, .env.example, docker-compose, Dockerfile, scripts/
2. Swagger documentation
- go get github.com/swaggo/http-swagger
- swag init produces docs/docs.go
- /swagger/index.html UI, /swagger/doc.json spec
- annotations on health/stats/ws and catalog handlers
3. Races API
- internal/races/{store,service,keyset,types}.go
- migration 005_races.sql: finished_races, race_plans, race_queue
- GET /api/races with keyset pagination, status filter
- GET /api/races/upcoming
- POST /api/races/queue/join, GET, DELETE
- POST/GET /api/races/plans, DELETE /{id}
- lobby.RaceMeta simplification (no host_id/host_name)
4. Races seeder
- internal/races/seed with deterministic generator
- --seed-races / --reset CLI flags in main.go
- 30 finished + 5 live + 5 plans + 4 queue entries
5. Drivers and clans
- internal/drivers, internal/clans (CRUD, validation)
- migration 008_drivers_clans.sql
- /api/clans and /api/drivers endpoints
- 3-letter uppercase nickname/tag validation
- lobby.DriverMeta: Nickname, AvatarURL, ClanID, ClanTag
6. Podium
- internal/transport.RacePodiumEntry
- lobby.SetDriverProfile for in-memory metadata sync
- migration 007_podium.sql (podium JSONB column)
- seeder populates top-3 per finished race
7. Live races persistence
- migration 009_live_persistence.sql: live_races,
live_race_drivers, lobby_drivers
- internal/races/live_store.go: LiveStore with write-side
mirror for lobby.Service mutations
- Service.collectLive and Upcoming read from Postgres
- main.go RestoreFromDB rehydrates lobby on startup
8. Unify live + finished into one races table
- migration 010_unify_races.sql: rename finished_races to
races, expand status CHECK, merge live rows
- PgStore now hosts both write paths; LiveStore is a
thin facade implementing lobby.Persistence
- seeder resetAll drops only finished/cancelled rows and
race_plans / race_queue / lobby_drivers
Each layer is consistent on its own; cross-layer changes are
visible in the file history. A future refactor may split this
commit into the per-stage boundaries listed above.
25 KiB
x0gp — Server Plan (Go)
Документ фиксирует серверную архитектуру: транспорт, модули, протокол, хранение, биллинг, деплой.
Дата фиксации архитектурных решений: 2026-06-18. Дата обновления (домашний сервер для PoC и MVP): 2026-06-18. Дата обновления v2 (Orange Pi 5 Pro + Coolify + статический IP): 2026-06-18.
Архитектурные выборы, на которых основан план:
- Транспорт клиент-сервер: WebSocket + бинарный protobuf.
- Управление машинками: Wi-Fi, ESP32-S3 агент.
- Модель доверия: authoritative server + client-side prediction.
- Позиционирование: IR-магнитные секторы + overhead-CV (гибрид).
1. Цели и не-цели
1.1. Цели
- Round-trip "команда на экране -> отклик машинки" в худшем случае 200 мс, в типичном 80-120 мс.
- Поддержка одновременно 50-100 активных заездов на 1 Edge-инстанс.
- Устойчивость к обрывам связи (клиент теряет сеть на 0.5-2 с без потери состояния).
- Anti-cheat: защита от подмены телеметрии, авто-команд, грязного руления.
- Биллинг заездов и турниров (ЮKassa для РФ, Stripe для остального).
- Горизонтальное масштабирование (stateless API, sticky-сессии на WS).
1.2. Не-цели (для MVP)
- Физика шин (скорость не считаем, считаем положение с CV).
- Запись/воспроизведение VR-реплеев.
- Полноценный matchmaking с ELO (только базовый лобби).
- Мобильное приложение (только Web, мобилка во второй очереди).
2. Высокоуровневая архитектура
+----------------------+
| React (Web client) |
| React Native (mobile)|
+----------+-----------+
|
WebSocket (binary protobuf)
|
+----------v-----------+
| Edge / API Gateway |
| (Go, stateless) |
+----+------+-----+----+
| | |
+-------------+ | +-------------+
| | |
+------v------+ +--------v-------+ +------v------+
| Race Engine | | Realtime Hub | | REST API |
| (stateful) | | (WS fan-out) | | (chi/echo) |
+-------------+ +----------------+ +-------------+
| | |
+---------+---------+ |
| |
+--------v--------+ |
| Redis | pub/sub, state |
+-----------------+ |
| |
+--------v--------+ |
| Postgres / TS |<-------------------+
+-----------------+
Edge / API Gateway — единая точка входа, TLS, rate-limit, auth, маршрутизация. Race Engine — authoritative state гонки, tick 60 Hz. Realtime Hub — WebSocket-концентратор, fan-out игрокам, сериализация. REST API — управление пользователями, лобби, платежами, история.
3. Транспорт
3.1. WebSocket (основной)
- Библиотека: gorilla/websocket или nbio (последняя — non-blocking, лучше для тысяч соединений).
- Формат кадров: бинарный protobuf (для latency) + опционально JSON для не-real-time сообщений (чат лобби).
- Subprotocol:
x0gp.v1. - Ping/pong: каждые 5 с, читаем RTT для диагностики.
3.2. Альтернатива на будущее
- WebRTC DataChannel — если потребуется суб-50 мс jitter.
- Собственный UDP+KCP — если появится конкурентная потребность.
- В плане не реализуем, но держим "port" в протоколе для миграции.
3.3. REST API
- HTTP/2, chi router.
- OpenAPI 3.1 спецификация, кодогенерация клиента.
- Авторизация: JWT (access 15 мин) + refresh token (30 дней).
- Idempotency-Key для платёжных эндпоинтов.
4. Структура Go-модулей
server/
cmd/
api-gateway/ # REST + WS, stateless
race-engine/ # stateful, authoritative tick
agent-linker/ # запускается в помещении (на gateway-ПК)
indexer/ # пишет телеметрию в Timescale
replay-recorder/ # (опц.) пишет replay в S3
internal/
auth/ # JWT, сессии, refresh, MFA
billing/ # ЮKassa/Stripe, биллинг сессий
race/ # сессии, круги, DNF, рестарты, секторы
realtime/ # WS hub, fan-out, per-client очереди
transport/ # protobuf-схемы, кодеки
control/ # приём команд, rate-limit, prediction snapshots
prediction/ # client-side prediction серверные корректировки
telemetry/ # агрегация, пересылка в TS
anti_cheat/ # эвристики: sanity check, повторы, geo
tournament/ # bracket, лэддер, регистрация
storage/
postgres/ # sqlx/pgx, миграции
redis/ # сессии, pub/sub, лобби
timescale/ # телеметрия
obs/ # логирование, метрики, трейсы
proto/
client_server.proto # команды, телеметрия для игрока
server_agent.proto # команды и состояние для машинки
api/
openapi.yaml
migrations/ # SQL
deploy/
docker-compose.yml # dev
helm/ # prod
scripts/
loadtest/ # 100 виртуальных гонщиков
5. Протокол (protobuf)
5.1. Клиент -> Сервер
ClientHello { version, token, locale }JoinRace { raceId, carClass }LeaveRace {}InputState { seq, tsMs, steering (-1..1), throttle (0..1), brake (0..1), gear (-1..0..1), buttons }(60-120 Hz)ChatMessage { text }(rate-limited)Ping { tsMs }
5.2. Сервер -> Клиент
ServerHello { sessionId, raceTick, config }RaceSnapshot { tick, tsMs, cars: [{id, x, y, heading, speed, lap, sector, lastLapMs}], leaderboard }(30-60 Hz)InputAck { seq, appliedAtMs }Correction { deltaSteering, deltaThrottle, reason }(редко, при расхождении)RaceEvent { type: START|LAP|SECTOR|DNF|END, payload }TournamentUpdate { ... }Pong { tsMs }
5.3. Сервер -> Бортовой агент
AgentCommand { carId, steering, throttle, brake, gear, flags, expiresAtMs }(60-120 Hz)AgentConfig { maxSteeringRate, maxThrottleRate, rampLimits }
5.4. Бортовой агент -> Сервер
AgentHello { carId, fwVersion, hwId, rssi }AgentTelemetry { carId, tsMs, steeringActual, throttleActual, rpm, voltage, current, tempEsc, tempMotor, imu: {ax, ay, az, gx, gy, gz}, irSensor: {front, rear}, batteryPct }(10-30 Hz)Heartbeat { tsMs, uptimeSec, errors }(1 Hz)AgentLog { level, msg }
5.5. Gateway-агент -> Edge-сервер
TrackHello { trackId, hwId, publicKey, version }CarFrame { carId, tsMs, position: {x,y,z}, heading, velocity, confidence }(из CV, 30-60 Hz)SectorEvent { carId, sector, tsMs, lap }ReplayChunk { raceId, tsMs, jpegBytes }(по запросу)LocalCommand { carId, payload }(forwarded from server)
6. Race Engine (authoritative tick)
6.1. Tick
- Фиксированный шаг: 16.67 мс (60 Hz).
- На каждом тике:
- Принять все InputState от игроков за последние N мс (батч).
- Применить к authoritative state машинок (с учётом ramp limits).
- Сформировать команды бортовым агентам.
- Отправить команды агенту в помещении.
- Через ~33 мс получить телеметрию и CV-обновления.
- Скорректировать state.
- Сформировать RaceSnapshot для клиентов.
6.2. Client-side prediction
- Клиент хранит последний применённый state и локально симулирует физику между snapshot-ами.
- При получении Snapshot клиент сверяет, если расхождение > порога — плавная коррекция (lerp 100-200 мс), без "телепорта".
- Сервер шлёт
Correctionтолько если расхождение > критического.
6.3. Anti-cheat эвристики
- Sanity check входящих команд (steering/throttle в допустимом диапазоне, скорость изменения не выше физического предела).
- Сравнение CV-позиции с заявленной (на сервере) — если расходятся > N метров, пометка DQF.
- Повторы replay: после заезда сверяем логгированные команды с CV-треком.
- Device fingerprint + поведенческий профиль.
- Rate limit: > 200 Hz InputState = бан.
6.4. Состояния гонки
LOBBY->COUNTDOWN(3..1) ->RACING->FINISHED.- DNF по: таймаут без input 5 с, потеря связи с агентом > 3 с, флаг администратора.
- RESTART: только на LoBBY или между раундами.
7. Realtime Hub
- Один WS-коннект на игрока.
- Подписки:
race:{raceId},tournament:{id},user:{id}. - Fan-out: при Snapshot race-движок публикует в Redis pub/sub, Hub читает и шлёт подписанным клиентам.
- Backpressure: если клиент не успевает читать — дропаем старые Snapshot, шлём только последний.
- Sticky-сессии: cookie
x0gp_stickyпри балансировке на L7.
8. Хранение
8.1. PostgreSQL (relational)
Таблицы (основные):
users(id, email, phone, password_hash, created_at, locale)sessions(id, user_id, refresh_hash, ip, ua, expires_at)wallets(user_id, balance, currency)payments(id, user_id, provider, ext_id, amount, status, created_at)races(id, track_id, class, started_at, ended_at, winner_id, replay_url)race_results(race_id, user_id, car_id, lap_times[], best_lap, dnf_reason)tournaments(id, name, format, entry_fee, prize_pool, status)tournament_entries(tournament_id, user_id, seed, eliminated_at)cars(id, hw_id, model, fw_version, status, last_seen)tracks(id, name, layout_id, geo_city, status)
Миграции: golang-migrate/migrate.
8.2. Redis
session:{token}-> userId (TTL 15 мин)refresh:{token}-> userId (TTL 30 дн)race:{id}:state-> serialized race state (TTL 1 час)lobby:{id}-> set of userIdsratelimit:{user}:{op}-> token bucket- pub/sub channel
race_events
8.3. TimescaleDB (time-series)
- hypertable
telemetry(ts, race_id, car_id, field, value) - 7 дней горячих данных, потом cold в S3.
- continuous aggregates для "лучший круг на треке", "топ-скорости".
9. Биллинг
9.1. Провайдеры
- ЮKassa — для РФ.
- Stripe — для остального мира (фаза 3).
9.2. Сценарии
- Пополнение кошелька (one-time payment).
- Списание за заезд (раз в N минут или за сессию).
- Входной взнос в турнир.
- Призовые начисления победителям.
9.3. Идемпотентность и реконсиляция
- Каждый платёж имеет
Idempotency-Keyна стороне клиента иext_idот провайдера. - Webhook-эндпоинт
/webhooks/yookassaс проверкой подписи. - Ежедневный job сверяет балансы с провайдером.
10. Авторизация
- Email + password (argon2id) — базовый сценарий.
- OAuth Yandex / VK / Google — для удобства.
- MFA TOTP — обязательно для админов и pro-игроков.
- JWT: HS256 в PoC, RS256 в проде.
- Refresh-token rotation, device-binding.
11. Anti-cheat
Уровни:
- Sanity — диапазоны, скорости, ramp limits (всегда).
- CV-check — расхождение с overhead-камерой > 1 м -> пометка.
- Replay verify — после заезда можно пересчитать (асинхронно).
- Behavioral — паттерны (слишком "идеальные" круги = подозрительно).
- Manual review — оператор смотрит replay при споре.
12. Observability
- Логи:
slog(stdlib) в JSON, отправка в Loki. - Метрики:
prometheus/client_golang, экспорт/metrics. - Трейсы: OpenTelemetry, OTLP в Jaeger/Tempo.
- Ключевые метрики:
ws_connections_totalrtt_ms_p50/p95/p99(по(clientSendTs - serverRecvTs))tick_lag_ms(если тик опаздывает > 5 мс — алерт).input_rate_per_clientdropped_snapshots_per_clientgateway_online{trackId}(heartbeat агенты в помещении).
13. Деплой и инфраструктура
Базовое решение: PoC и MVP крутятся на Orange Pi 5 Pro через Coolify. Перенос на облако/VPS — когда появится коммерческая нагрузка и SLA обязательства.
13.1. Orange Pi 5 Pro (PoC и MVP)
Платформа: Orange Pi 5 Pro (Rockchip RK3588S, ARM64). Уже есть в наличии.
Характеристики:
- CPU: 8 cores (4× Cortex-A76 @ 2.4 GHz + 4× Cortex-A55 @ 1.8 GHz)
- RAM: 4 / 8 / 16 / 32 GB LPDDR4x (зависит от версии)
- GPU: Mali-G610 MP4
- NPU: 6 TOPS (RKNN) — для CV-инференса (YOLO/ByteTrack) на борту
- Сеть: 2× 2.5 GbE (один WAN, один LAN с трассой — физическая изоляция)
- Wi-Fi 6 (резерв)
- USB 3.0 × 2, PCIe 2.1 x1, SATA
- ОС: Ubuntu (уже стоит, с Coolify)
Coolify как платформа деплоя:
- Self-hosted PaaS, уже развёрнут.
- UI для управления сервисами, доменами, SSL, env-vars.
- Push-to-deploy из Git-репо (GitHub/Gitea).
- Сервисы = Docker Compose под капотом, но с UI и без боли.
- Встроенные: PostgreSQL, Redis, MinIO (S3-совместимое), Plausible, Uptime Kuma.
- Версионирование деплоев, rollback в 1 клик.
Доступ из интернета (статический IP уже есть):
- Основной вариант: Caddy reverse proxy + Let's Encrypt напрямую. Coolify умеет провизить LE-сертификаты из коробки.
- Подходит, т.к. провайдер даёт статический IP, 443/tcp открыт на роутере через port-forward на Orange Pi.
- Cloudflare Tunnel не нужен (нет смысла добавлять лишний hop).
- WAF: Crowdsec через Coolify (опц., можно отложить).
Архитектура:
+---------------- ORANGE PI 5 PRO (Ubuntu + Coolify) ----------------+
| |
| Coolify (docker compose orchestration) |
| +-- api-gateway ------------------------------------+ |
| +-- race-engine ----------------------------------+ |
| +-- postgres+timescale (через Coolify UI) | |
| +-- redis -----------------------------------+ |
| +-- caddy (LE) -----------------------------------+ |
| +-- prometheus+grafana -----------------------------------+ |
| +-- minio (S3 replays) -----------------------------------+ |
| |
| Natively (systemd) - требуют прямой доступ к железу: |
| +-- agent-linker (Wi-Fi к машинкам) |
| +-- cv-tracker (USB камеры, NPU RKNN) |
| +-- node_exporter (мониторинг) |
| |
+---------------------------------------------------------------------+
2.5 GbE #1 (WAN, к роутеру) 2.5 GbE #2 (LAN к трассе)
| |
vv
+--------------+ +---------------------+
| Internet | | Switch (или точка- |
| (clients) | | точка) |
+--------------+ +----------+----------+
|
+------v------+
| x0gp-track |
| AP + камеры |
+-------------+
Изоляция сети трассы:
- LAN-порт Orange Pi → выделенный коммутатор (или напрямую к точке доступа x0gp-track).
- В этой сети только машинки и камеры — никаких домашних устройств.
- AP настраивается на изолированный SSID / VLAN.
- Это даёт предсказуемый latency (нет соседского трафика) и безопасность.
Использование NPU RK3588S:
- RKNN Toolkit 2 для конвертации моделей (PyTorch → ONNX → RKNN).
- Поддерживаются YOLOv8/v9, ByteTrack через onnxruntime-rknn.
- Целевой инференс: 60-120 FPS на одной камере 1080p (с запасом).
- Если NPU не подходит — fallback на CPU ARM A76.
- Альтернативный inference-сервер: Ultralytics + onnxruntime-rknn.
ARM64-специфика:
- Docker-образы должны быть
linux/arm64илиmulti-arch. - Postgres+TimescaleDB, Redis, Caddy, Prometheus, Grafana — есть для arm64.
- Go-сервисы —
GOARCH=arm64при сборке (по умолчанию если собирать на Orange Pi). - CV-инференс на Python — pin версии onnxruntime-rknn под aarch64.
Бэкапы:
- Ежедневный
pg_dump(cron в Coolify или отдельный scheduled task). - Backup в MinIO (S3-совместимое) внутри Coolify.
- Опционально off-site: rclone → Backblaze B2 / Yandex Object Storage.
- Retention: 14 дней локально, 30 дней off-site.
Безопасность:
- SSH: только ключи, fail2ban.
- Coolify UI: за reverse proxy с basic auth или Cloudflare Access.
- Порты наружу: 80/tcp + 443/tcp (для Caddy/LE).
- Postgres и Redis слушают только 127.0.0.1 внутри Coolify.
- UPS для Orange Pi (microSD карты не любят внезапные отключения).
13.2. Production (после MVP, когда появится коммерческая нагрузка)
- VPS в Москве и СПб (2 региона) — Selectel / Yandex Cloud / Timeweb.
- Managed Postgres + Redis (для снятия с Orange Pi задач бэкапа и HA).
- Kubernetes (Yandex Cloud / Selectel / AWS) — HPA по метрикам ws_connections, tick_lag.
- Redis Cluster, Postgres с read-replica.
- CDN для статики (CloudFront / Cloudflare).
- WAF (CrowdSec).
- Orange Pi остаётся как gateway-агент + CV-инференс в помещении с трассой (NPU продолжаем использовать для позиционирования).
- Edge-сервер (Go) переезжает на VPS, к которому подключаются несколько Orange Pi из разных помещений.
14. Производительность и SLO
| Метрика | Цель |
|---|---|
| API p99 latency | < 100 мс |
| WS input->ack | < 30 мс на Edge |
| Tick stability | 60 Hz +/- 2 мс |
| Один race-engine | 100 активных гонок |
| Один api-gateway | 5000 WS-клиентов |
| Доступность | 99.5% (PoC), 99.9% (prod) |
15. Roadmap (server)
| Фаза | Срок | Что делаем |
|---|---|---|
| S0: PoC моно-сервис | 1-2 нед | Один Go-бинарь: WS + REST + race state in-memory + локальный agent-linker. SQLite. Без биллинга. |
| S1: MVP модульный | 4-6 нед | Разделение на api-gateway / race-engine / agent-linker. Postgres + Redis. Auth, JWT, лобби. |
| S2: CV + телеметрия | 8-10 нед | Интеграция с overhead-CV. TimescaleDB. Replay recorder (S3-совместимое). |
| S3: Биллинг | 10-12 нед | ЮKassa, кошелёк, сессии под оплатой. Webhooks. Админка. |
| S4: Турниры | 14-18 нед | Bracket, лэддер, призовые. Анти-чит уровня 2-3. |
| S5: Продакшн | 4-6 мес | K8s, мульти-регион, SLO, on-call. Stripe для остального мира. Мобильное приложение. |
16. Открытые вопросы
- Стек frontend: React + Vite, какие библиотеки для WS (reconnect,
backoff)? Кандидаты:
reconnecting-websocket,socket.io-client(overkill, но быстро), свой тонкий клиент наWebSocketAPI. - Cloudflare Tunnel vs WireGuard vs статический IP — что выберем для доступа к домашнему серверу? Дефолт: Cloudflare Tunnel (проще, есть free-tier, базовый WAF). Уточним после тестов latency.
- Бэкапы Postgres на NAS — есть ли дома NAS или использовать внешнее S3-совместимое (Backblaze B2 / Yandex Object Storage) для off-site бэкапов?
- CDN/streaming для replay — Mux / Cloudflare Stream / своё? Out of scope для MVP, но в плане держим "порт".
- Интеграция со стриминговыми платформами (Twitch) — out of scope для MVP, но в плане держим "порт".
- Язык CV-сервиса: Go (gocv) или Python (быстрее итерация, ONNX-рантайм)? Дефолт: Python на PoC для скорости итераций, Go для prod-инференса (меньше ресурсов, проще деплой).
- Конкретные характеристики домашнего сервера (CPU/RAM/диск/ОС) — нужно для оценки, сколько одновременных гонок он потянет. В PLAN.md пока заложен разумный минимум (4 CPU / 16 GB / 500 GB SSD).
- ЮKassa-аккаунт — есть ли уже ИП/ООО для приёма платежей, или регистрировать при переходе на биллинг?