Лучший бэкенд для realtime браузерной игры: стек и архитектура
Практичный разбор стеков для realtime мультиплеерной браузерной игры: протоколы, архитектура, языки, масштабирование и стоимость — без лишнего шума, с продакшн-нюансами.

Если промахнуться с бэкендом для realtime мультиплеера, вы платите дважды: деньгами за железо, которое не тянет пиковые тики, и репутацией — лаги, десинхрон, абьюзеры. Исправлять потом в три раза дороже, чем построить правильно изначально.
Коротко: лучший бэкенд для браузерной multiplayer-игры — авторитетный игровой сервер (authoritative), транспорт WebSocket или WebRTC DataChannel, тики 20–30 Гц (для большинства жанров), синхронизация state через дельты, Redis для матчмейкинга и эфемерного состояния, Postgres для метаданных/платежей, горизонтальное масштабирование по комнатам. По языку: Node.js для скорости запуска, Go — для предсказуемой производительности, Rust — для максимальной эффективности, Elixir/BEAM — для сверхконкурентных задач. Дальше — подробности, архитектура и деньги.
Что значит «лучший бэкенд» для браузерного realtime
«Лучший» — это сочетание шести ограничений:
- Задержка: целим 50–120 мс RTT по региону. Всё выше — требует агрессивной интерполяции/предсказаний.
- Пропускная способность: 10–50 КБ/с на игрока в среднем для action-жанров при 20–30 Гц и дельта-компрессии.
- Стабильность тиков: джиттер сервера важнее среднего CPU. 1–2 пропущенных тика подряд — заметный рывок.
- Масштабирование: тысячи комнат, устойчивое распределение нагрузки, отказоустойчивость.
- Безопасность: сервер — авторитет. Клиент не решает исход коллизий, урона, лута. Анти-чит — на стороне сервера.
- Стоимость: железо и трафик съедают бюджет гораздо быстрее, чем база данных.
Для браузера мы живём в реальности HTTP(S), WebSocket и WebRTC DataChannel. Чистого UDP у клиента нет, значит всё пронизано надёжными протоколами поверх UDP/TCP с особенностями NAT и шифрования.
Архитектура по умолчанию для 90% кейсов
В большинстве проектов мы рекомендуем такую схему:
- Cloudflare (или NGINX+HAProxy) — TLS termination, WebSocket proxy, DDoS mitigation.
- API/Backend (REST/GraphQL) — логин, инвентарь, матчмейкинг, платежи (Stripe), хранение профилей (Postgres).
- Matchmaker — Redis Streams/Sorted Sets для очередей, NATS для событий.
- Room Server — авторитетная игровая логика: ECS/physics/ability-cooldowns; протокол WebSocket или WebRTC DC.
- State Sync — дельты + снапшоты, бинарный протокол (например, FlatBuffers/Protocol Buffers/MessagePack).
- Storage — Postgres (персистент), Redis (эфемерный state/leaderboards), объектное хранилище (реплеи/ассеты).
- Observability — Prometheus + Grafana, Sentry, p99 latency трейсинг (OpenTelemetry), логирование (Loki/Elastic).
Поток:
- Клиент авторизуется и получает токен комнаты.
- Обращается к матчмейкеру → получает адрес ближайшего регионального Room Server.
- Устанавливает WebSocket/WebRTC соединение.
- Сервер запускает луп тиков (например, 30 Гц), принимает инпуты, обновляет мир, рассылает дельты состояния.
- По окончании матча метрики и результаты уезжают в Postgres; быстрозаводимые счётчики — в Redis.
Если вы ещё выбираете движок для рендера на клиенте, посмотрите наш разбор сравнение Three.js/Babylon.js/Pixi.js — это влияет на частоту апдейтов и формат пакетов.
Протокол: WebSocket vs WebRTC (и немного про WebTransport)
Сводка по транспорту для браузера:
| Транспорт | Плюсы | Минусы | Когда брать |
|---|---|---|---|
| WebSocket | Простота, обратная совместимость, дешёвый прокси через Cloudflare, зрелые библиотеки | TCP head-of-line blocking, нет P2P, всё через сервер | Большинство PvP/PvE матчей до 50–100 игроков на комнату |
| WebRTC DataChannel | UDP под капотом, частично надёжные каналы, P2P возможен, хорош для голосового чата | Сложность: STUN/TURN, ICE, отладка; TURN-трафик дорог | Игры, чувствительные к джиттеру; браузер-к-браузеру или для смешанных медиа |
| WebTransport (HTTP/3) | QUIC, частичная надёжность, сервер проще, чем WebRTC | Ещё не везде доступен, экосистема молода | Перспектива на 1–2 года вперёд; пилоты |
По нашему опыту как строителей, 80% браузерных проектов стартуют с WebSocket и живут отлично. WebRTC имеет смысл, если нужно P2P/voice или вас душит TCP HOL-blocking при активном обновлении состояния. P2P для честных матчей — спорная идея: авторитетным должен оставаться сервер.
Язык и фреймворк сервера: скорость разработки vs скорость тиков
Короткий ориентир:
| Язык/стек | Сильные стороны | Слабые стороны | Типичный use-case |
|---|---|---|---|
| Node.js (uWebSockets.js, Colyseus) | Быстрый старт, богатая экосистема, хорошая DX, много готовых решений | GC-паузы, однопоточность (хотя есть Worker Threads), нужно следить за backpressure | Инди и midcore, time-to-market критичен |
| Go (netpoll, nhooyr/websocket) | Предсказуемые t-latency, низкие паузы GC, простое деплой/кросс-компиляция | Меньше «из коробки» гейм-фреймворков | Сессионные MMO-lite, стабильные 30–60 Гц, тысячи комнат |
| Rust (Tokio, quinn, webrtc-rs) | Максимум производительности и контроля, нулевые паузы GC | Больше времени на разработку, высокая планка входа | Жанры, где CPU-тяжёлая физика и строгие SLAs |
| Elixir (Phoenix Channels, OTP) | Миллионы соединений, fault-tolerance, отличное наблюдение, hot reload | Низкоуровневого контроля над памятью меньше | Огромное количество комнат/чатов, мидкор с умеренной физикой |
Тонкость: «быстрый» язык не починит плохую архитектуру. Падение производительности обычно из-за неоптимальной сериализации/дельт и лишних аллокаций, а не из-за «не того языка».
Минимальный сервер на Node.js с тиками и дельтами
Для скорости демонстрации покажем sketched-подход на TypeScript с uWebSockets.js. Он даёт низкие накладные по сравнению с популярными ws/Socket.IO и хорошо дружит с бинарными протоколами.
// package.json: uWebSockets.js, msgpackr
import uWS from 'uWebSockets.js';
import { pack } from 'msgpackr';
const TICK_HZ = 30;
const clients = new Map<number, uWS.WebSocket>();
let nextId = 1;
// Игровой стейт (упрощённо): позиции игроков и версии снапшота
type Vec2 = { x: number; y: number };
const state: Record<number, Vec2> = {};
let snapshotVersion = 0;
function applyInput(id: number, input: { dx: number; dy: number }) {
const p = state[id] || { x: 0, y: 0 };
p.x += input.dx;
p.y += input.dy;
state[id] = p;
}
function computeDeltas(prevVersion: number) {
// В реальности используем хэши/битовые маски; тут — вся карта позиций
return { v: snapshotVersion, players: state };
}
uWS
.App()
.ws('/room', {
compression: uWS.DEDICATED_COMPRESSOR_3KB,
maxPayloadLength: 16 * 1024,
idleTimeout: 120,
upgrade: (res, req, context) => {
res.upgrade({},
req.getHeader('sec-websocket-key'),
req.getHeader('sec-websocket-protocol'),
req.getHeader('sec-websocket-extensions'),
context
);
},
open: (ws) => {
const id = nextId++;
(ws as any).id = id;
clients.set(id, ws);
state[id] = { x: 0, y: 0 }; // spawn
// Отправим текущий снапшот
ws.send(pack({ t: 'snapshot', v: snapshotVersion, players: state }));
},
message: (ws, message, isBinary) => {
try {
const data = JSON.parse(Buffer.from(message).toString());
if (data.t === 'input') applyInput((ws as any).id, data);
} catch {}
},
close: (ws) => {
const id = (ws as any).id;
clients.delete(id);
delete state[id];
snapshotVersion++;
}
})
.listen(9001, (token) => {
if (!token) throw new Error('Port busy');
console.log('Room listening on :9001');
});
// Тик-луп: обновление и рассылка дельт
setInterval(() => {
snapshotVersion++;
const payload = pack({ t: 'delta', ...computeDeltas(snapshotVersion - 1) });
for (const [id, ws] of clients) {
// Backpressure: не шлём, если сокет перегружен
if (ws.getBufferedAmount() < 64 * 1024) ws.send(payload, true);
}
}, 1000 / TICK_HZ);
Пояснения:
- Используем бинарную сериализацию (
msgpackr), следим заgetBufferedAmount()— иначе улетим в лаг из-за переполнения буфера. - Реальный проект держит «версионированные» снапшоты и рассылает только изменившиеся компоненты (ECS-компоненты), добавляет аутентификацию токеном и миграцию игроков между процессами при рестартах.
Сервер на Go будет выглядеть компактнее по памяти; на Rust — ещё экономнее и предсказуемее по p99. Но TypeScript часто выигрывает спринт MVP → траектория монетизации.
Синхронизация состояния: тики, предсказание, интерполяция
- Тик-частота: 20–30 Гц достаточно для MOBA/экшенов с умеренным TTK. 60 Гц имеет смысл, когда локальная физика перетекает в исход боя (и у вас бюджет на железо).
- Клиентское предсказание: инпуты применяются локально сразу, сервер присылает authoritative-стейт; клиент делает корректировку (reconciliation) при расхождениях.
- Интерполяция/экстраполяция: для других игроков рендерим интерполяцию между последними снапшотами; при потере пакетов переключаемся на кратковременную экстраполяцию.
- Дельта-компрессия: храните bitset изменённых компонентов и кодируйте изменения компактно. 60–80% трафика обычно экономится именно тут, а не на zlib.
- Квантование: координаты/углы в
int16/int32в мирных единицах — против «сырых» float.
Сервер должен чётко различать: вход игрока (input), результаты симуляции (snapshot), и side-effects (события/аудио/эффекты), иначе вы смешаете каналы и получите ненужный трафик. Один сухой совет: не передавайте состояние, которое можно детерминированно воспроизвести на клиенте.
Масштабирование: комнаты, регионы, балансировка
- Комнаты = процесс/актори. Самый простой и надёжный юнит масштабирования. В Kubernetes — по pod на N комнат, stickiness по
roomId. - Matchmaker оперирует пулами комнат и публикует события в NATS/Redis Streams. Комната регистрируется в сервис-дискавери (Consul/etcd или headless-сервис в K8s).
- Region routing: резолвим ближайший регион по RTT/GeoIP. Пользователь может перезаписать выбор, если играет с другом.
- Sticky sessions: по WebSocket — через cookie или query token → балансировщик должен поддерживать sticky по хешу.
- Горизонт: тысячи комнат = тысячи соединений между сервисами. Думайте об observability с первого дня: метрики на комнату, не «в целом по кластеру».
Сохранение состояния при рестартах — боль многих инди. Решение: быстрый снапшот комнаты (инкрементально) в Redis/объектку с TTL 10–30 сек; при рестарте — тёплый рекавери и soft-reconnect игроков. Это дешевле, чем вечная «высокая доступность» одной комнаты.
Что ломается в продакшене
- Backpressure: если игнорировать, то WebSocket-буферы раздуваются, GC зашкаливает, тики рваные. Лечится проверкой
bufferedAmount, передачей приоритезированных пакетов и капингом частоты рассылок. - GC-паузы и аллокации: особенно в Node.js при частых JSON-парсах. Решение — бинарные протоколы, object pooling, избегать временных объектов внутри тика.
- Nagle/Delayed ACK: на TCP убедитесь, что
TCP_NODELAYвключён (библиотеки реального времени обычно это делают). - ICE/TURN расходы: WebRTC через TURN может умножить трафик и счет. Самостоятельный
coturn+ агрессивный STUN и fallback на WebSocket для data-only — сбалансированная схема. - Десинхрон из-за дрейфа часов: не полагайтесь на
Date.now()для симуляции; у сервера должен быть authoritative time, у клиента — локальный смещение-дрифт. - DDoS/WebSocket flood: rate limit на gateway (Cloudflare Rules), токены с коротким TTL, circuit breakers на уровне комнаты.
- Сохранение результатов: слишком частые записи в Postgres во время матча → contention. Записывайте итоги батчами после матча, используйте idempotent операции.
Классическая шутка: если ваша игра иногда «телепортирует» игроков — это либо читер, либо вы слишком оптимистично сжимали дельты. Или и то, и другое.
Экономика и сроки: сколько стоит и когда окупается
Примерные ориентиры (региональная оценка, без маркетинга):
- MVP (браузерный PvP 2–10 игроков/комната, 30 Гц, WebSocket, авторитетный сервер): 4–8 недель, команда 2–4 инженера. Бюджет разработки: $30k–$120k в зависимости от контента и интеграций.
- Инфраструктура на 5k CCU по региону: 6–12 узлов c8a/cpu-оптимизированные (AWS/GCP), Redis (managed), Postgres (HA), Cloudflare. Оценка $2k–$8k/мес + исходящий трафик.
- TURN/голос: дополнительно $300–$3000/мес при активном использовании.
- Тестирование нагрузки и телеметрия: 1–2 недели. Без этого вы будете «гадать» на проде.
Где горят деньги:
- Трафик и неэффективная сериализация (в 2–5 раз больше байтов, чем нужно).
- Громоздкая физика на сервере без профайлинга.
- Плохой матчмейкинг (долгие очереди → люди уходят → CAC вырастает).
Окупаемость приходит тогда, когда матч стабилен (ни лагов, ни дропов), а экономика игры удерживает сессии. Технический долг мультиплеера редко чинится «по ходу» без пауз.
Когда брать какой стек
- Инди/MVP, быстрый рынок, 2D/изометрия, комнаты до 20 игроков: Node.js + uWebSockets.js/Colyseus; Redis; Postgres; WebSocket. Сборка через Docker, деплой на Fly.io/Render или k8s в managed-кластере.
- Средний онлайн, стабильные 30–60 Гц, упор на предсказуемость и простое масштабирование: Go +
nhooyr/websocketилиfasthttp+ Redis/NATS + Postgres. Kubernetes, horizontal pod autoscaler. - Жёсткие SLAs, CPU-тяжёлая симуляция, баттлы с большим количеством объектов: Rust + Tokio, бинарная сериализация (FlatBuffers/Cap’n Proto), специализированный профайлинг.
- Крайняя конкуренция по соединениям/акторам, hot upgrades, сложная оркестрация комнат: Elixir + Phoenix Channels/LiveView, кластеризация через libcluster.
Если планируете выпускаться не только в браузере, но и в десктопе/Steam, смотрите наш текст о том, как выпустить игру в Steam, будучи веб-студией — нюансы билда и дистрибуции влияют на сетевую архитектуру и тестирование.
Инструменты и практики, которые экономят месяцы
- Набор протокольных тестов: реплеи входов, симуляция джиттера/потерь, автосравнение снепшотов.
- Канарейка на 1–5% игроков при выкладках. Feature flags и пер-игроковая телеметрия.
- Единый формат сообщений: один прото-файл/IDL для сервера и клиента; автогенерация кодеков.
- Отдельный канал для reliability-critical событий (hit/kill) и для «косметики». Разные приоритеты в очереди отправки.
- Бюджет трафика на дизайн-стадии. Спорьте о килобайтах, пока все трезвы.
FAQ
Можно ли сделать полноценный мультиплеер на Firebase/Firestore без собственного сервера?
Можно сделать прототип. Но как только потребуется авторитетная логика, стабильные тики и защита от читов, нужен собственный сервер. Firestore/RTDB не предоставляют нужной детерминированности и пинг-ориентированного обмена, а стоимость операций быстро улетает.
Serverless (AWS Lambda/Cloudflare Workers) подойдёт для комнат?
Для матчмейкинга, лобби, inventory — да. Для авторитетной логики с тиками — нет: нужен долгоживущий процесс, предсказуемый CPU-слайс и сокеты. Иначе вы упираетесь в холодные старты и ограничения соединений.
Какой тикрейт выбрать: 20, 30 или 60 Гц?
20–30 Гц — по умолчанию для большинства браузерных экшенов. 60 Гц — когда механики критичны к задержке и у вас достаточно бюджета на оптимизацию и железо. Важнее стабильность тика (джиттер), чем сам номинал.
Имеет ли смысл P2P между браузерами через WebRTC для экономии серверов?
Редко. Авторитет всё равно у сервера, иначе — читы и десинхрон. P2P часто превращается в relay через TURN → вы платите трафик и усложняете сеть. Лучше экономьте байты дельтами и квантованием.
Какую базу выбрать для игровых данных?
- Постоянные данные (аккаунты, инвентарь, платежи) — Postgres.
- Эфемерные (очереди матчей, присутствие, счётчики) — Redis.
- Логи/телеметрия — ClickHouse/BigQuery. Документные БД годятся, но чаще не нужны.
Как бороться с читами в браузере?
Сервер — авторитет на всё значимое. Клиент ничего «не решает»: попадания, урон, лут — на сервере. Сигнатуры подозрительных паттернов, трейсинг инпутов, rate limits. Браузерные анти-чит средства ограничены, поэтому участь чита — бан по поведенческим метрикам.
Key takeaways
- Архитектура по умолчанию: авторитетный Room Server, WebSocket/WebRTC, дельта-компрессия, Redis+Postgres, горизонтальное масштабирование по комнатам.
- Язык выбирайте по целям: Node.js — скорость запуска, Go — предсказуемость, Rust — максимальная эффективность, Elixir — сверхконкурентность.
- Производительность чаще упирается в сериализацию и backpressure, а не «в язык».
- Тики 20–30 Гц хватает для большинства браузерных жанров; 60 Гц дороже и сложнее.
- Планируйте наблюдаемость и нагрузочные тесты с первого спринта — иначе будете чинить на проде.
- Экономьте байты: квантование, дельты, приоритетные каналы отправки важнее «магических оптимизаций».
Если вы строите realtime мультиплеер в браузере и хотите пройти от прототипа до стабильного продакшена без лишних трат, MTBYTE может спроектировать архитектуру, собрать MVP и подготовить масштабирование. Напишите нам: /contact.
СЛЕДУЮЩИЙ ШАГ
Понравилось как мыслим?
Применяем те же принципы в клиентских проектах: AI, автоматизации, продукты, которые не умирают после релиза.