Architektura serwerów Zero-Waste: Analiza propozycji hibernacji w ramach optymalizacji serwerów Fortnite
Każdy twórca gier multiplayer w końcu uderza w tę samą finansową ścianę: Twoja infrastruktura serwerowa spala pieniądze na symulowanie pustej przestrzeni. Kiedy uruchamiasz Dedicated Server dla wielkoskalowego battle royale, survivalu czy MMO, cykle CPU są w ogromnym stopniu marnowane na obliczenia w stanie bezczynności. Płacisz najwyższe stawki za Cloud Computing, aby obliczać grawitację dla kamieni, na które nikt nie patrzy, przetwarzać nawigację AI dla wrogów bez celów i utrzymywać World States w sektorach całkowicie pozbawionych aktywności graczy.
Niedawno w społeczności Unreal Engine pojawiła się fascynująca propozycja techniczna skierowana do kierownictwa Epic Games. Główna teza? Masowa skala Fortnite wymaga przejścia z scentralizowanego, kosztownego w utrzymaniu modelu hostingu na model „Zero-Waste Infrastructure”. Autor argumentował, że eliminując straty w symulacji, Epic mógłby obniżyć Operating Expenses (Opex) o 60-70%, co teoretycznie pozwoliłoby im obniżyć cenę waluty premium do poziomu 1,99 USD za 1000 V-dolców.
Podczas gdy ekonomiczna wykonalność cen V-dolców jest tematem dla projektantów monetyzacji, techniczny filar tej propozycji — Sector Physics Hibernation (SGH) — to prawdziwy majstersztyk nowoczesnej architektury serwerowej.
W tej analizie branżowej rozłożymy na czynniki pierwsze mechanikę propozycji hibernacji w ramach optymalizacji serwerów Fortnite, zbadamy, jak działa Logic-Side Culling w Unreal Engine 5, i zademonstrujemy, jak możesz wdrożyć infrastrukturę zero-waste we własnych tytułach multiplayer.
Matematyka strat symulacji
Aby zrozumieć, dlaczego Sector Physics Hibernation jest konieczna, musimy spojrzeć na brutalną matematykę serwerów dedykowanych.
Weźmy standardową mapę battle royale o powierzchni 100 km². Na początku meczu 100 graczy ląduje w różnych punktach zainteresowania. W ciągu pierwszych 5 minut 50% graczy zostaje wyeliminowanych, a pozostali zbiegają się w kierunku kurczącej się Safe Zone.
Do 10. minuty ponad 70% całkowitej powierzchni mapy zawiera zero aktywnych graczy. Mimo to, w standardowej konfiguracji Authoritative Server, serwer dedykowany nadal przetwarza Tick całego stanu świata z częstotliwością 30 Hz.
- Physics Calculations: Rigid Bodies, zniszczalne środowiska i balistyka są nadal śledzone w pamięci.
- Actor Ticking: Tysiące instancji
AActorwywołują swoje funkcjeTick()30 razy na sekundę. - NavMesh Processing: Błąkające się AI lub dynamiczne przeszkody nadal odpytują Navigation Mesh.
Jeśli uruchamiasz serwery na instancjach AWS c5.2xlarge, płacisz około 0,34 USD za godzinę za maszynę. Jeśli jedna maszyna może hostować tylko dwie instancje gry dla 100 graczy, ponieważ CPU jest maksymalnie obciążone obliczaniem pustej przestrzeni, Twoja skalowalność jest poważnie ograniczona.
Propozycja sugeruje, że odzyskując ten zmarnowany overhead CPU, deweloperzy mogą albo upakować 5-6 instancji gry na tym samym sprzęcie (obniżając rachunki za serwery o ok. 60%), albo przekierować odzyskaną moc obliczeniową na zwiększenie globalnego Tick Rate serwera z 30 Hz do 60 Hz+, zapewniając idealną Hit Registration i płynną rozgrywkę.
Deep Dive: Sector Physics Hibernation w UE5
Proponowane rozwiązanie techniczne opiera się na wykorzystaniu istniejącego systemu World Partition w Unreal Engine 5, ale zmieniając jego główne zastosowanie z zarządzania pamięcią po stronie klienta na zarządzanie CPU po stronie serwera.
Problem z domyślnymi serwerami dedykowanymi
Domyślnie World Partition w UE5 wczytuje i usuwa komórki dla klienta na podstawie ich odległości od źródła streamingu (kamery gracza). Jest to doskonałe do utrzymania niskiego zużycia pamięci klienta i wysokiej liczby klatek na sekundę.
Jednak Dedicated Server zazwyczaj ładuje całą mapę do pamięci, aby zachować autorytatywność. Jeśli snajper wystrzeli pocisk przez dolinę lub jeśli zostanie wyzwolone globalne wydarzenie, serwer potrzebuje danych o kolizjach i Actor States dostępnych od ręki, aby zweryfikować akcję. Dynamiczne ładowanie i usuwanie danych z dysku na serwerze (Level Streaming) jest często zbyt wolne i powoduje ogromne przycięcia, niszcząc Tick Rate.
Rozwiązanie SGH: Logic-Side Culling
Zamiast usuwać sektor z pamięci (co powoduje wąskie gardła IO), Sector Physics Hibernation proponuje CPU-Sleep States.
Sektor pozostaje w pamięci RAM, ale wszystkie Ticki, obliczenia fizyki i aktualizacje stanu są agresywnie wstrzymywane. Gdy komórka Spatial Grid danego sektora wykryje zero aktywnych bytów (graczy, pojazdów graczy lub aktywnych pocisków), serwer zawiesza alokację CPU dla tej konkretnej siatki.
Implementacja Hibernation Manager w C++
Aby zbudować to w Unreal Engine, potrzebujesz podsystemu, który monitoruje komórki Spatial Grid i dynamicznie przełącza stan Tick aktorów w ich obrębie. Poniżej znajduje się uproszczony prototyp architektoniczny implementacji 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; // Sprawdzaj co 2 sekundy
TimeSinceLastCheck = 0.0f;
GridCellSize = 10000.0f; // Komórki siatki 100m
}
void USectorHibernationManager::Tick(float DeltaTime)
{
TimeSinceLastCheck += DeltaTime;
if (TimeSinceLastCheck >= HibernationCheckInterval)
{
EvaluateSectors();
TimeSinceLastCheck = 0.0f;
}
}
void USectorHibernationManager::EvaluateSectors()
{
UWorld* World = GetWorld();
if (!World) return;
// Krok 1: Mapowanie pozycji aktywnych graczy na komórki siatki
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)
);
// Oznacz tę komórkę i sąsiednie jako aktywne (strefa buforowa)
MarkAdjacentCellsActive(CellCoord, ActiveCells);
}
}
// Krok 2: Iteracja po aktorach podlegających hibernacji i przełączanie Tick
for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
{
AActor* Actor = *ActorItr;
// Pomiń kluczowych aktorów infrastruktury
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())
{
// Wybudzenie
Actor->SetActorTickEnabled(true);
Actor->SetActorEnableCollision(true);
}
else if (!bShouldBeActive && Actor->IsActorTickEnabled())
{
// Uśpienie
Actor->SetActorTickEnabled(false);
// Opcjonalnie: Zredukuj kolizje tylko do prostych zapytań, aby oszczędzić czas wątku fizyki
Actor->SetActorEnableCollision(false);
}
}
}
Złożoność fazy „Wake-Up”
Powyższy kod ilustruje podstawową koncepcję, ale prawdziwym wyzwaniem inżynieryjnym jest faza wybudzania. Jeśli gracz wystrzeli z karabinu snajperskiego o wysokiej prędkości wylotowej w stronę uśpionego sektora, pocisk przekroczy granicę, zanim 2-sekundowa pętla ewaluacyjna go wychwyci.
Jeśli sektor obudzi się po dotarciu pocisku, nastąpi katastrofalny desync. Pocisk może przelecieć prosto przez hibernujący pojazd, ponieważ jego kolizja była wyłączona. To zjawisko jest ściśle powiązane z problemami opisanymi w naszym przewodniku The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It, gdzie rozbieżności czasowe między stanem serwera a predykcją klienta całkowicie niszczą symulację.
Aby to rozwiązać, infrastruktura zero-waste wymaga Predictive Wake-Ups. Zamiast tylko śledzić pozycje graczy, serwer musi obliczać wektory prędkości wszystkich aktywnych pocisków i szybkich pojazdów. Jeśli wektor przecina uśpioną komórkę siatki, serwer musi natychmiast wymusić wybudzenie tej konkretnej komórki przed przybyciem obiektu.
Orkiestracja serwerów Zero-Waste na dużą skalę
Wdrożenie Logic-Side Culling w silniku gry to tylko połowa sukcesu. Druga połowa to orkiestracja infrastruktury.
Jeśli Twój dedykowany serwer UE5 skutecznie i dynamicznie redukuje zużycie CPU o 60%, Twoje środowisko hostingowe musi być wystarczająco inteligentne, aby rozpoznać ten spadek i upakować nowe instancje gry na tym samym węźle sprzętowym.
Samodzielne zbudowanie takiej orkiestracji wymaga ogromnej wiedzy z zakresu DevOps. Musiałbyś wdrożyć klastry Kubernetes, skonfigurować Agones do zarządzania cyklem życia serwerów gier, napisać niestandardowe metryki skalowania oparte na utylizacji CPU i zarządzać Load Balancers, aby kierować graczy do odpowiednich instancji. To łatwo 4-6 miesięcy dedykowanej pracy nad infrastrukturą — czas zabrany bezpośrednio z tworzenia gry.
Dzięki horizOn te usługi orkiestracji backendu są wstępnie skonfigurowane. Platforma obsługuje dynamiczne upakowanie instancji, Auto-Scaling oparty na obciążeniu serwera w czasie rzeczywistym oraz zautomatyzowane potoki wdrażania dla Twoich buildów serwerów dedykowanych. Pozwalając wyspecjalizowanemu Backend-as-a-Service zająć się infrastrukturą, możesz skupić się na wydaniu gry multiplayer, zamiast spędzać pół roku na walce z manifestami Kubernetes.
Co więcej, gdy upakowujesz więcej instancji na jednym węźle, zwiększasz ryzyko problemów typu Noisy Neighbor wpływających na Twój wątek sieciowy. Zabezpieczenie Netcode przed tymi wąskimi gardłami jest krytyczne, co szczegółowo omawiamy w The Uefn Server Performance Exploit Explained Hard Armoring Your Unreal Engine Netcode.
Najlepsze praktyki dla architektury Multiplayer Zero-Waste
Niezależnie od tego, czy budujesz battle royale dla 100 graczy, czy survival w otwartym świecie, wdrożenie hibernacji i technik zero-waste wymaga ścisłej dyscypliny architektonicznej. Oto pięć sprawdzonych w boju praktyk, które pozwolą utrzymać niskie Opex serwera bez poświęcania wrażeń graczy:
1. Odseparuj Game State od pętli Tick
Największym wrogiem wydajności serwera jest ciągłe odpytywanie danych (polling). Nigdy nie używaj Tick() do sprawdzania, czy wydarzenie powinno nastąpić. Przejdź całkowicie na Event-Driven Architecture. Jeśli ognisko ma zgasnąć po 5 minutach, nie odliczaj czasu w każdym Ticku. Ustaw Timer Delegate, który odpali się dokładnie raz po 300 sekundach. Pozwala to aktorowi ogniska pozostać w całkowitym uśpieniu przez 4 minuty i 59 sekund.
2. Wdróż agresywne NetCullDistanceSquared
Unreal Engine decyduje, których aktorów replikować do których klientów na podstawie NetCullDistanceSquared. Wielu deweloperów zostawia tu wartości domyślne, zmuszając serwer do serializacji i kompresji danych dla aktorów oddalonych o setki metrów od gracza. Przeprowadź audyt dystansów Cull. Porzucona broń nie musi być replikowana dalej niż 5000 jednostek (50 metrów). Oblicz absolutne minimalne promienie wymagane dla Twojej pętli rozgrywki i rygorystycznie je egzekwuj.
3. Używaj Spatial Hash Grids dla zapytań O(1)
Przy obliczaniu, którzy aktorzy powinni zostać uśpieni, iterowanie po każdym aktorze w świecie (TActorIterator) staje się wąskim gardłem przy 100 000 bytów. Wdróż Spatial Hash Grid. Gdy aktor się porusza, aktualizuje swoją pozycję w mapie mieszającej. Pozwala to menedżerowi hibernacji na zapytanie „Co jest w komórce siatki X?” w czasie O(1), czyniąc ewaluację hibernacji praktycznie darmową dla CPU.
4. Wykorzystuj Buffer Zones dla płynnego wybudzania
Nigdy nie hibernuj sektora tuż przy granicy pola widzenia gracza. Zawsze utrzymuj „Buffer Zone” aktywnych sektorów o szerokości co najmniej jednej komórki siatki wokół każdego aktywnego bytu. Jeśli Twoje komórki mają 100 metrów szerokości, a gracz jest w komórce A, wszystkie sąsiednie komórki (siatka 3x3) muszą pozostać w pełni aktywne. Gwarantuje to, że jeśli gracz nagle przekroczy granicę, komórka docelowa jest już zainicjowana i przetwarza Ticki.
5. Regularnie profiluj buildy Dedicated Server
Nie zgaduj, co pożera Twój procesor. Użyj Unreal Insights w spakowanym środowisku serwera dedykowanego z symulowanym obciążeniem. Patrz szczególnie na czasy GameThread. Jeśli widzisz, że Physics lub TickTime dominują w wykresie wątku, gdy gracze stoją w miejscu, Twoja logika hibernacji zawodzi. Telemetria to jedyny sposób, aby potwierdzić, że architektura zero-waste działa w rzeczywistości, a nie tylko w teorii.
Przyszłość Opex serwerów
Propozycja społeczności Fortnite rzuca światło na kluczową prawdę: obecny standard branżowy polegający na wymuszaniu wydajności serwerów za pomocą drogiego Cloud Compute jest nie do utrzymania. W miarę jak światy stają się większe, a liczba graczy rośnie, liniowe skalowanie kosztów infrastruktury powoli wykrwawi budżety Live-Ops.
Sector Physics Hibernation, Logic-Side Culling i dynamiczne upakowanie instancji to już nie tylko optymalizacje dla studiów AAA; to wymogi przetrwania dla gier multiplayer każdej wielkości. Przyjmując podejście zero-waste na wczesnym etapie cyklu deweloperskiego, zapewniasz, że rentowność Twojej gry skaluje się wraz z bazą graczy.
Jeśli jesteś gotowy na wdrożenie dynamicznego skalowania serwerów bez bólów głowy związanych z DevOps, wypróbuj horizOn za darmo lub sprawdź dokumentację API, aby zobaczyć, jak bezproblemowa może być infrastruktura multiplayer.