Назад к блогу

Архитектура Zero-Waste серверов: анализ предложения по гибернации для оптимизации серверов Fortnite

Опубликовано 25 февраля 2026 г.
Архитектура Zero-Waste серверов: анализ предложения по гибернации для оптимизации серверов Fortnite

Каждый разработчик Multiplayer-игр рано или поздно упирается в одну и ту же финансовую стену: ваша серверная инфраструктура сжигает деньги на симуляцию пустого пространства. Когда вы запускаете Dedicated Server для масштабной Battle Royale, Survival-игры или MMO, циклы CPU в основном тратятся на холостые вычисления. Вы платите по премиальным тарифам Cloud Computing за расчет гравитации для камней, на которые никто не смотрит, обработку AI-навигации для врагов без целей и поддержание World States в секторах, где вообще нет игроков.

Недавно в сообществе Unreal Engine появилось захватывающее техническое предложение, адресованное руководству Epic Games. Основной тезис? Огромный масштаб Fortnite требует перехода от централизованной модели хостинга к модели «Zero-Waste Infrastructure». Автор утверждает, что, устранив избыточную симуляцию, Epic могла бы сократить Operating Expenses (Opex) на 60-70%, что теоретически позволило бы снизить цену на премиальную валюту до $1.99 за 1000 V-Bucks.

Хотя экономическая целесообразность цены на V-Bucks — это вопрос к дизайнерам монетизации, техническая основа этого предложения — Sector Physics Hibernation (SGH) — является мастер-классом по современной серверной архитектуре.

В этом анализе индустрии мы разберем механику предложения по гибернации для оптимизации серверов Fortnite, изучим, как работает Logic-Side Culling в Unreal Engine 5, и покажем, как вы можете внедрить Zero-Waste инфраструктуру в свои собственные Multiplayer-проекты.

Математика избыточной симуляции

Чтобы понять, почему необходима Sector Physics Hibernation, нужно взглянуть на суровую математику выделенных игровых серверов.

Возьмем стандартную карту Battle Royale площадью 100 км². В начале матча 100 игроков высаживаются в разных точках интереса. В течение первых 5 минут 50% игроков выбывают, а выжившие стягиваются к сужающейся Safe Zone.

К 10-й минуте более 70% общей площади карты не содержат ни одного активного игрока. Тем не менее, в стандартной конфигурации Authoritative Server выделенный сервер продолжает обрабатывать Tick всего состояния мира с частотой 30 Гц.

  • Physics Calculations: Rigid Bodies, разрушаемое окружение и баллистика по-прежнему отслеживаются в памяти.
  • Actor Ticking: Тысячи экземпляров AActor вызывают свои функции Tick() 30 раз в секунду.
  • NavMesh Processing: Блуждающий AI или динамические препятствия продолжают опрашивать Navigation Mesh.

Если вы запускаете свои серверы на инстансах AWS c5.2xlarge, вы платите примерно $0.34 в час за машину. Если одна машина может хостить только два игровых инстанса на 100 игроков, потому что CPU перегружен расчетом пустого пространства, ваше масштабирование сильно ограничено.

Предложение гласит, что, вернув эти впустую потраченные ресурсы CPU, разработчики могут либо разместить 5-6 игровых инстансов на том же оборудовании (сократив счета за серверы на ~60%), либо перенаправить освободившуюся мощность на увеличение глобального Tick Rate сервера с 30 Гц до 60 Гц+, обеспечивая идеальную Hit Registration и плавный геймплей.

Глубокое погружение: Sector Physics Hibernation в UE5

Предложенное техническое решение опирается на использование существующей системы World Partition в Unreal Engine 5, но с переключением ее основного сценария использования с управления памятью на стороне клиента на управление CPU на стороне сервера.

Проблема стандартных Dedicated Servers

По умолчанию World Partition в UE5 подгружает и выгружает ячейки для клиента в зависимости от их расстояния до источника стриминга (камеры игрока). Это отлично подходит для поддержания низкого потребления памяти клиентом и высокого FPS.

Однако Dedicated Server обычно загружает всю карту в память для поддержания авторитетности. Если снайпер делает выстрел через долину или срабатывает глобальное событие, серверу нужны данные о коллизиях и Actor States в быстром доступе для валидации действия. Динамическая загрузка и выгрузка данных с диска на сервере (Level Streaming) часто происходит слишком медленно и вызывает огромные задержки, убивая Tick Rate.

Решение SGH: Logic-Side Culling

Вместо выгрузки сектора из памяти (что создает узкие места IO), Sector Physics Hibernation предлагает CPU-Sleep States.

Сектор остается в RAM, но все тики, физические расчеты и обновления состояния агрессивно приостанавливаются. Когда ячейка Spatial Grid сектора обнаруживает отсутствие активных сущностей (игроков, транспорта игроков или активных снарядов), сервер приостанавливает выделение ресурсов CPU для этой конкретной сетки.

Реализация Hibernation Manager на C++

Чтобы построить это в Unreal Engine, вам понадобится подсистема, которая отслеживает ячейки Spatial Grid и динамически переключает состояние Tick акторов внутри них. Ниже приведен упрощенный архитектурный прототип реализации SectorHibernationManager.

#include "SectorHibernationManager.h"
#include "EngineUtils.h"
#include "GameFramework/Actor.h"
#include "GameFramework/PlayerController.h"

void USectorHibernationManager::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    HibernationCheckInterval = 2.0f; // Проверка каждые 2 секунды
    TimeSinceLastCheck = 0.0f;
    GridCellSize = 10000.0f; // Ячейки сетки 100м
}

void USectorHibernationManager::Tick(float DeltaTime)
{
    TimeSinceLastCheck += DeltaTime;
    if (TimeSinceLastCheck >= HibernationCheckInterval)
    { 
        EvaluateSectors();
        TimeSinceLastCheck = 0.0f;
    }
}

void USectorHibernationManager::EvaluateSectors()
{
    UWorld* World = GetWorld();
    if (!World) return;

    // Шаг 1: Сопоставление позиций активных игроков с ячейками сетки
    TSet<FIntVector> ActiveCells;
    for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
    { 
        APlayerController* PC = Iterator->Get();
        if (PC && PC->GetPawn())
        { 
            FVector PlayerPos = PC->GetPawn()->GetActorLocation();
            FIntVector CellCoord = FIntVector(
                FMath::FloorToInt(PlayerPos.X / GridCellSize),
                FMath::FloorToInt(PlayerPos.Y / GridCellSize),
                FMath::FloorToInt(PlayerPos.Z / GridCellSize)
            );
            
            // Помечаем эту и соседние ячейки как активные (буферная зона)
            MarkAdjacentCellsActive(CellCoord, ActiveCells);
        }
    }

    // Шаг 2: Итерация по акторам, поддерживающим гибернацию, и переключение Tick
    for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
    { 
        AActor* Actor = *ActorItr;
        
        // Пропускаем важные инфраструктурные акторы
        if (!Actor->ActorHasTag(FName("Hibernatable"))) continue;

        FVector ActorPos = Actor->GetActorLocation();
        FIntVector ActorCell = FIntVector(
            FMath::FloorToInt(ActorPos.X / GridCellSize),
            FMath::FloorToInt(ActorPos.Y / GridCellSize),
            FMath::FloorToInt(ActorPos.Z / GridCellSize)
        );

        bool bShouldBeActive = ActiveCells.Contains(ActorCell);
        
        if (bShouldBeActive && !Actor->IsActorTickEnabled())
        { 
            // Пробуждение
            Actor->SetActorTickEnabled(true);
            Actor->SetActorEnableCollision(true);
        }
        else if (!bShouldBeActive && Actor->IsActorTickEnabled())
        { 
            // Засыпание
            Actor->SetActorTickEnabled(false);
            // Опционально: Снижаем коллизию до простых запросов для экономии времени физического потока
            Actor->SetActorEnableCollision(false); 
        }
    }
}

Сложность фазы «Пробуждения»

Приведенный выше код иллюстрирует основную концепцию, но настоящая инженерная задача заключается в фазе пробуждения. Если игрок стреляет из высокоскоростной снайперской винтовки в спящий сектор, снаряд пересечет границу до того, как 2-секундный цикл оценки это заметит.

Если сектор проснется после того, как пуля прилетит, вы столкнетесь с катастрофическим десинком. Пуля может пролететь прямо сквозь гибернирующий транспорт, потому что его коллизия была отключена. Это явление тесно связано с проблемами, описанными в нашем руководстве The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It, где расхождения во времени между состоянием сервера и предсказанием клиента полностью ломают симуляцию.

Чтобы решить эту проблему, Zero-Waste инфраструктура требует Predictive Wake-Ups. Вместо того чтобы просто отслеживать позиции игроков, сервер должен рассчитывать векторы скорости всех активных снарядов и высокоскоростного транспорта. Если вектор пересекается со спящей ячейкой сетки, сервер должен мгновенно принудительно пробудить эту конкретную ячейку до прибытия объекта.

Оркестрация Zero-Waste серверов в масштабе

Внедрение Logic-Side Culling внутри игрового движка — это только половина дела. Вторая половина — оркестрация инфраструктуры.

Если ваш выделенный сервер UE5 успешно и динамично сокращает потребление CPU на 60%, ваша среда хостинга должна быть достаточно умной, чтобы распознать это падение и разместить новые игровые инстансы на том же узле оборудования.

Создание такой оркестрации самостоятельно требует огромного объема DevOps-инженерии. Вам нужно будет развернуть кластеры Kubernetes, настроить Agones для управления жизненным циклом игровых серверов, написать кастомные метрики масштабирования на основе загрузки CPU и управлять Load Balancers для маршрутизации игроков. Это легко может занять 4-6 месяцев работы над инфраструктурой — время, отнятое у разработки самой игры.

С horizOn эти сервисы оркестрации бэкенда уже преднастроены. Платформа берет на себя динамическую упаковку инстансов, Auto-Scaling на основе нагрузки в реальном времени и автоматизированные пайплайны развертывания для ваших билдов Dedicated Server. Позволив специализированному Backend-as-a-Service управлять инфраструктурой, вы сможете выпустить свою Multiplayer-игру вместо того, чтобы полгода сражаться с манифестами Kubernetes.

Более того, когда вы размещаете больше инстансов на одном узле, вы увеличиваете риск проблем с Noisy Neighbor, влияющих на ваш сетевой поток. Защита вашего Netcode от этих узких мест критически важна — эту тему мы подробно освещаем в The Uefn Server Performance Exploit Explained Hard Armoring Your Unreal Engine Netcode.

Лучшие практики для Zero-Waste Multiplayer архитектуры

Независимо от того, строите ли вы Battle Royale на 100 игроков или персистентный Survival в открытом мире, внедрение гибернации и Zero-Waste техник требует строгой архитектурной дисциплины. Вот пять проверенных практик, которые помогут сохранить низкий Opex сервера без ущерба для игрового опыта:

1. Отделите Game State от Tick Loop

Главный враг производительности сервера — постоянный опрос данных (polling). Никогда не используйте Tick() для проверки того, должно ли произойти событие. Полностью перейдите на Event-Driven Architecture. Если костер должен погаснуть через 5 минут, не делайте тик каждый кадр для вычитания времени. Установите таймер-делегат, который сработает ровно один раз через 300 секунд. Это позволит актору костра полностью «спать» в течение 4 минут и 59 секунд.

2. Внедрите агрессивный NetCullDistanceSquared

Unreal Engine определяет, какие акторы реплицировать каким клиентам, на основе NetCullDistanceSquared. Многие разработчики оставляют значения по умолчанию, заставляя сервер сериализовать и сжимать данные для акторов, находящихся в сотнях метров от игрока. Проведите аудит дистанций отсечения. Выброшенное оружие не нужно реплицировать дальше 5000 единиц (50 метров). Рассчитайте абсолютный минимальный радиус, необходимый для вашего геймплея, и строго его соблюдайте.

3. Используйте Spatial Hash Grids для поиска за O(1)

При расчете того, какие акторы должны уснуть, итерация по каждому актору в мире (TActorIterator) сама по себе становится узким местом, если у вас 100 000 сущностей. Внедрите Spatial Hash Grid. Когда актор движется, он обновляет свою позицию в хэш-карте. Это позволяет вашему Hibernation Manager запрашивать «Что находится в ячейке сетки X?» со сложностью O(1), делая оценку гибернации практически бесплатной для CPU.

4. Используйте Buffer Zones для плавного пробуждения

Никогда не отправляйте сектор в гибернацию прямо на границе видимости игрока. Всегда поддерживайте «буферную зону» активных секторов шириной как минимум в одну ячейку сетки вокруг любой активной сущности. Если ваши ячейки имеют ширину 100 метров, а игрок находится в ячейке А, то все соседние ячейки (сетка 3x3) должны оставаться полностью активными. Это гарантирует, что если игрок внезапно пересечет границу, целевая ячейка уже будет полностью инициализирована и активна.

5. Регулярно профилируйте билды Dedicated Server

Не гадайте, что съедает ваш CPU. Используйте Unreal Insights в среде упакованного выделенного сервера с симулированной нагрузкой. Обращайте особое внимание на тайминги GameThread. Если вы видите, что Physics или TickTime доминируют на графике потока, когда игроки стоят на месте, ваша логика гибернации не работает. Телеметрия — единственный способ подтвердить, что ваша Zero-Waste архитектура функционирует в реальности, а не только в теории.

Будущее серверных Opex

Предложение сообщества Fortnite проливает свет на важную истину: нынешний отраслевой стандарт «грубой силы» серверной производительности с помощью дорогого Cloud Compute неустойчив. По мере того как миры становятся больше, а количество игроков растет, линейное масштабирование затрат на инфраструктуру будет медленно истощать бюджеты Live-Ops.

Sector Physics Hibernation, Logic-Side Culling и динамическая упаковка инстансов — это больше не просто оптимизации для AAA-студий; это требования выживания для Multiplayer-игр любого масштаба. Приняв Zero-Waste мышление на ранних этапах разработки, вы гарантируете, что прибыльность вашей игры будет расти вместе с ее аудиторией.

Если вы готовы внедрить динамическое масштабирование серверов без головной боли DevOps, попробуйте horizOn бесплатно или изучите документацию API, чтобы увидеть, насколько бесшовной может быть Multiplayer-инфраструктура.


Источник: [Technical Proposal] Unified Operational Sovereignty: Decoupling Opex to Enable a $1.99 V-Buck Economy