Real-Time Backend
Design: Multiplayer Game
Fortnite, 2018 год: 12 млн игроков одновременно в Battle Royale. Каждый из них двигается, стреляет, строит - и видит consistent картину мира. Как это работает без рассинхронизации и читов?
- Fortnite: 350 млн зарегистрированных игроков, пик 12 млн concurrent - Epic строил authoritative server инфраструктуру на AWS с региональными game server clusters
- Valve CS2: 128-tick серверы означают, что каждые 7.8 мс сервер фиксирует полный снапшот мира и рассылает дельты 20+ игрокам одновременно
- Riot Games LoL: matchmaking обрабатывает миллионы запросов в пиковые часы с SBMM на основе TrueSkill-подобного алгоритма с uncertainty factor
- Dead by Daylight: rollback netcode позволяет комфортно играть при разнице пинга между игроками до 150 мс без ощущения 'rubber banding'
Authoritative Server Architecture
Fortnite достиг 12 млн одновременных игроков в пике. За этой цифрой стоит одна архитектурная ставка: **authoritative server model**. Сервер - единственный источник истины о состоянии игры. Клиент отправляет только намерения (inputs), получает обратно результаты. Никакого доверия клиенту.
Альтернатива - peer-to-peer (P2P) - использовалась в ранних RTS и файтингах. Каждый клиент рассчитывает физику локально и синхронизирует дельты. Проблема: читы прямолинейно реализуются модификацией локального клиента, а рассинхронизация состояния (desync) почти неизбежна при нестабильных соединениях.
Valve Source Engine работает на 66 тиках в секунду для большинства серверов, до 128 тиков для CS2 в соревновательном режиме. Tick rate - частота, с которой сервер обрабатывает входящие команды и рассылает обновления состояния. 128 тиков = каждые 7.8 мс сервер фиксирует снапшот мира.
Riot Games для League of Legends выбрали tick rate 150 мс (примерно 6-7 тиков/с) - намеренно низкий. MOBA с изометрическим видом терпимее к задержкам, чем шутер от первого лица. Riot экономит серверные ресурсы и снижает требования к сети для игроков из регионов с высоким пингом.
- **Authoritative server** - сервер рассчитывает физику, проверяет коллизии, применяет урон
- **Client-side prediction** - клиент локально предсказывает результат своего input, чтобы убрать субъективную задержку
- **Server reconciliation** - клиент корректирует предсказание когда приходит серверный снапшот
- **Lag compensation** - сервер перематывает состояние назад в момент выстрела, чтобы компенсировать сетевую задержку игрока
Зачем в authoritative server модели клиент не имеет права самостоятельно применять урон другому игроку?
Lobby System
Лобби - это waiting room перед игрой. Задача простая на вид: собрать группу игроков, позволить лидеру выбрать настройки, запустить матч. На практике - это отдельный stateful микросервис с нетривиальными требованиями к консистентности.
Steam обрабатывает миллионы лобби одновременно через свой Lobby API. Каждое лобби хранит: список участников, метаданные (карта, режим, приватность), состояние (ожидание/готовность/запуск). Ключевой выбор - где хранить это состояние.
- Redis (in-memory) — Низкая латентность чтения/записи (< 1 мс). Идеально для volatile данных лобби - они живут 5-15 минут. TTL автоматически чистит брошенные лобби. Риск: при рестарте Redis данные теряются - нужна persistence или репликация.
- PostgreSQL — ACID гарантии - никаких race conditions при одновременном join двух игроков. Дороже по латентности (5-20 мс), но надёжнее для финансовых операций (платное лобби, ставки). Избыточно для бесплатных casual игр.
Dead by Daylight использует лобби размером 5 (1 killer + 4 survivors). Каждый член лобби хранит свой character selection и ready status. Матч начинается только когда все 5 готовы И matchmaking нашёл подходящий game server. Если сервер не найден за 30 секунд - лобби возвращается в состояние waiting.
Критическая проблема лобби - **split-brain при disconnect**. Хост потерял соединение в момент, когда все готовы к запуску. Два подхода: host migration (передать роль хоста следующему участнику) или server-authoritative lobby (сервер сам принимает решение о запуске независимо от хоста).
Почему лобби данные часто хранят в Redis, а не в PostgreSQL?
Matchmaking
Matchmaking решает задачу: найти набор игроков, которые получат сбалансированную и честную игру, с минимальным временем ожидания. Это многокритериальная оптимизация. Критерии часто противоречат друг другу.
- **Skill (MMR/ELO)** - разрыв в рейтинге не должен быть катастрофическим
- **Latency** - пинг всех участников к game server должен быть приемлемым (< 100 мс)
- **Wait time** - игрок не должен ждать дольше 2-3 минут
- **Party integrity** - группа друзей должна попасть в один матч на одну сторону
- **Region** - игроки из одного региона предпочтительнее
Riot Games публично описывал свой matchmaking для LoL: используется ELO-подобная система MMR (Match Making Rating). При поиске матча система сначала ищет противников в узком диапазоне рейтинга. Если за 30 секунд не находит - расширяет диапазон. Через 2 минуты диапазон максимален.
Fortnite с 350 млн зарегистрированных игроков использует региональные matchmaking пулы. Северная Америка, Европа, Азия - отдельные очереди с отдельными серверными регионами. Трансрегиональные матчи возможны только в непиковые часы когда пул слишком мал.
Для skill-based matchmaking (SBMM) критически важна актуальность MMR. После каждого матча рейтинг пересчитывается. Современные системы используют Bayesian подход (TrueSkill от Microsoft): рейтинг хранится как распределение (mean + sigma), а не одно число. Это позволяет точнее оценивать игроков с малым количеством игр.
Почему matchmaking расширяет диапазон MMR со временем, а не сразу ищет в широком диапазоне?
Game State Sync
Игровой сервер создан, матч начался. Теперь задача: синхронизировать состояние мира между всеми участниками с минимальной задержкой. Наивный подход - рассылать полный snapshot каждый тик. Для Fortnite с 100 игроками это катастрофа: 100 * 100 bytes * 66 ticks = ~660 KB/s на одного клиента.
Valve Source Engine использует **delta compression**: клиенту отправляется только изменение состояния относительно последнего подтверждённого снапшота. Если позиция игрока не изменилась - она не отправляется. Это снижает трафик в 5-10 раз в типичных сценариях.
- **Full state snapshot** - отправлять всё каждый тик. Просто, но дорого по трафику
- **Delta updates** - только изменения с последнего ACK. Сложнее, но эффективнее
- **Interest management** - отправлять только то, что находится в радиусе видимости игрока
- **Priority queue** - важные события (попадания, смерти) отправляются первыми при перегрузке сети
**Rollback netcode** - техника, популяризованная файтингами (Dead by Daylight, Guilty Gear Strive). Вместо ожидания подтверждения от сервера клиент предсказывает действия оппонента и рендерит локально. Если предсказание неверно - откатывает состояние назад и пересчитывает. Субъективная задержка исчезает, но могут быть визуальные глитчи при расхождении.
- Lockstep (delay-based) — Все участники ждут подтверждения от каждого участника перед симуляцией следующего тика. Детерминированный результат, нет desync. Проблема: один игрок с высоким пингом тормозит всех остальных. Использовался в Starcraft, Age of Empires.
- Rollback netcode — Каждый клиент предсказывает чужие действия и симулирует локально. При получении реальных данных - откат и пересимуляция. Комфортен для игроков с разным пингом. Требует полностью детерминированную и быструю симуляцию. Используется в файтингах, инди-играх.
Client-side prediction означает, что клиент сам решает исход действий и сервер принимает это решение
Client-side prediction - это только локальная визуализация предполагаемого результата. Сервер всегда валидирует и может откорректировать
Если бы сервер принимал клиентские предсказания как истину - любой читер мог бы предсказать телепортацию в любую точку карты. Предсказание существует исключительно для устранения субъективной задержки в UI.
Что такое lag compensation на game server?
Итоги
- **Authoritative server** - клиент отправляет только inputs, сервер рассчитывает всё и является единственным источником истины; это единственная защита от читов
- **Lobby** - stateful waiting room в Redis с TTL; хранит участников, настройки, ready-статусы; требует host migration при disconnect лидера
- **Matchmaking** - многокритериальная оптимизация (skill + latency + wait time); диапазон MMR динамически расширяется с временем ожидания
- **Game state sync** - delta compression снижает трафик в 5-10 раз; interest management фильтрует невидимые entities; lag compensation перематывает историю для честной проверки попаданий
Связанные темы
Multiplayer backend строится на фундаментальных концепциях realtime систем:
- WebSocket и realtime протоколы — Game server использует persistent connections (WebSocket или UDP + QUIC) для low-latency обмена state updates
- Distributed systems: consistency — Authoritative server - это single point of truth, решающий проблему distributed consistency через централизацию
- Load balancing и session affinity — Game sessions требуют sticky routing - все пакеты одного матча должны идти на один game server process
Вопросы для размышления
- Fortnite переключился с P2P на authoritative server при масштабировании. Какие конкретные проблемы с читами и desync стали катализатором этого перехода?
- При rollback netcode клиент может видеть игровые объекты прыгающими назад при коррекции предсказания. Как современные игры минимизируют этот визуальный артефакт?
- Если game server падает в середине матча - как организовать быстрый failover без полной потери прогресса партии?