블로그로 돌아가기

제로 웨이스트 서버 설계: 포트나이트 서버 최적화 하이버네이션 제안 분석

게시일 2026년 2월 25일
제로 웨이스트 서버 설계: 포트나이트 서버 최적화 하이버네이션 제안 분석

모든 Multiplayer 개발자는 결국 동일한 재정적 장벽에 부딪힙니다. 바로 서버 인프라가 빈 공간을 시뮬레이션하기 위해 비용을 낭비하고 있다는 점입니다. 대규모 Battle Royale, 서바이벌 게임 또는 MMO를 위해 Dedicated Server를 가동하면 CPU 사이클은 유휴 계산에 심하게 치우치게 됩니다. 아무도 보지 않는 바위의 중력을 계산하고, 타겟이 없는 적의 AI 내비게이션을 처리하며, 플레이어 활동이 전혀 없는 섹터의 World States를 유지하기 위해 프리미엄 Cloud Computing 요금을 지불하고 있는 것입니다.

최근 Unreal Engine 커뮤니티에서 Epic Games 경영진을 향한 흥미로운 기술 제안이 제기되었습니다. 핵심 논지는 무엇일까요? Fortnite의 거대한 규모를 감당하려면 유지 보수 비용이 많이 드는 중앙 집중식 호스팅 모델에서 "Zero-Waste Infrastructure" 모델로 전환해야 한다는 것입니다. 제안자는 시뮬레이션 낭비를 제거함으로써 Epic이 Operating Expenses (Opex)를 60-70% 절감할 수 있으며, 이론적으로 1,000 V-Bucks의 가격을 $1.99 MSRP까지 낮출 수 있다고 주장했습니다.

V-Buck 가격 책정의 경제적 타당성은 수익화 디자이너들의 논의 과제이겠지만, 이 제안의 기술적 핵심인 **Sector Physics Hibernation (SGH)**은 현대 서버 아키텍처의 정수를 보여줍니다.

본 산업 분석에서는 포트나이트 서버 최적화 하이버네이션 제안의 메커니즘을 분석하고, Unreal Engine 5에서 Logic-Side Culling이 어떻게 작동하는지 탐구하며, 여러분의 Multiplayer 타이틀에 제로 웨이스트 인프라를 구현하는 방법을 시연해 보겠습니다.

시뮬레이션 낭비의 수학

왜 Sector Physics Hibernation이 필요한지 이해하려면 Dedicated Server의 냉혹한 수학적 현실을 살펴봐야 합니다.

표준적인 100km² Battle Royale 지도를 예로 들어보겠습니다. 매치 시작 시 100명의 플레이어가 다양한 Point of Interest에 낙하합니다. 처음 5분 이내에 플레이어의 50%가 탈락하고, 생존한 플레이어들은 좁아지는 Safe Zone으로 모여듭니다.

10분이 지나면 지도 전체 면적의 70% 이상에 활성 플레이어가 한 명도 없게 됩니다. 그럼에도 불구하고 표준 Authoritative Server 설정에서 Dedicated Server는 30Hz로 전체 월드 상태를 계속 Tick합니다.

  • Physics Calculations: Rigid Bodies, 파괴 가능한 환경, 탄도 계산이 여전히 메모리에서 추적됩니다.
  • Actor Ticking: 수천 개의 AActor 인스턴스가 초당 30번씩 Tick() 함수를 호출합니다.
  • NavMesh Processing: 배회하는 AI나 동적 장애물이 Navigation Mesh를 계속 쿼리합니다.

AWS c5.2xlarge 인스턴스에서 서버를 실행하는 경우 머신당 시간당 약 $0.34를 지불합니다. 단일 머신이 빈 공간을 계산하느라 CPU가 한계에 도달해 100인용 게임 인스턴스를 두 개만 호스팅할 수 있다면, 스케일 확장에 심각한 병목 현상이 발생합니다.

이 제안은 낭비되는 CPU 오버헤드를 회수함으로써 개발자가 동일한 하드웨어에 5-6개의 게임 인스턴스를 패킹하여 서버 비용을 약 60% 절감하거나, 회수된 처리 능력을 전역 서버 Tick Rate를 30Hz에서 60Hz+로 높이는 데 재투자하여 완벽한 Hit Registration과 부드러운 게임플레이를 보장할 수 있다고 제안합니다.

딥 다이브: UE5의 Sector Physics Hibernation

제안된 기술적 솔루션은 Unreal Engine 5의 기존 World Partition 시스템을 활용하되, 주요 사용 사례를 클라이언트 측 메모리 관리에서 서버 측 CPU 관리로 전환하는 데 의존합니다.

기본 Dedicated Server의 문제점

기본적으로 UE5의 World Partition은 스트리밍 소스(플레이어 카메라)와의 거리에 따라 클라이언트를 위해 셀을 로드하거나 언로드합니다. 이는 클라이언트 메모리 사용량을 낮게 유지하고 프레임 레이트를 높게 유지하는 데 탁월합니다.

하지만 Dedicated Server는 권한 유지를 위해 보통 전체 지도를 메모리에 로드합니다. 저격수가 계곡 너머로 총을 쏘거나 전역 이벤트가 발생할 경우, 서버는 동작을 검증하기 위해 충돌 데이터와 Actor States를 즉시 사용할 수 있어야 하기 때문입니다. 서버에서 디스크로부터 데이터를 동적으로 로드 및 언로드하는 방식(Level Streaming)은 대개 너무 느리고 심각한 히치(Hitches)를 유발하여 Tick Rate를 망가뜨립니다.

SGH 솔루션: Logic-Side Culling

메모리에서 섹터를 언로드하는 대신(IO 병목 유발), Sector Physics Hibernation은 CPU-Sleep States를 제안합니다.

섹터는 RAM에 유지되지만 모든 Tick, 물리 계산 및 상태 업데이트는 공격적으로 일시 중지됩니다. 섹터의 Spatial Grid 셀이 활성 엔티티(플레이어, 플레이어 소유 차량 또는 활성 투사체)를 감지하지 못하면 서버는 해당 특정 그리드에 대한 CPU 할당을 중단합니다.

C++로 하이버네이션 매니저 구현하기

Unreal Engine에서 이를 구축하려면 Spatial Grid 셀을 모니터링하고 그 안의 Actor들의 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; // 100m 그리드 셀
}

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단계: 하이버네이션 가능한 Actor들을 순회하며 Tick 토글
    for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
    { 
        AActor* Actor = *ActorItr;
        
        // 필수 인프라 Actor는 건너뜀
        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); 
        }
    }
}

"깨우기(Wake-Up)" 단계의 복잡성

위의 코드는 핵심 개념을 보여주지만, 실제 엔지니어링 과제는 깨우기 단계에 있습니다. 플레이어가 잠자고 있는 섹터를 향해 고속 저격 소총을 발사하면, 투사체는 2초의 평가 루프가 이를 포착하기 전에 경계를 통과하게 됩니다.

총알이 도착한 후에 섹터가 깨어나면 치명적인 Desync가 발생합니다. 충돌이 비활성화되어 있었기 때문에 총알이 하이버네이션 중인 차량을 그대로 통과할 수 있습니다. 이 현상은 서버 상태와 클라이언트 예측 사이의 타이밍 불일치가 시뮬레이션을 완전히 망가뜨리는 문제를 다룬 The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It 가이드의 내용과 밀접하게 관련되어 있습니다.

이를 해결하기 위해 제로 웨이스트 인프라는 Predictive Wake-Ups를 필요로 합니다. 단순히 플레이어 위치를 추적하는 대신, 서버는 모든 활성 투사체와 고속 차량의 전방 속도 벡터를 계산해야 합니다. 벡터가 잠자고 있는 그리드 셀과 교차하면 서버는 물체가 도착하기 전에 해당 특정 셀에 대해 즉시 깨우기 이벤트를 강제해야 합니다.

대규모 제로 웨이스트 서버 오케스트레이션

게임 엔진 내부에서 Logic-Side Culling을 구현하는 것은 절반의 성공일 뿐입니다. 나머지 절반은 인프라 오케스트레이션입니다.

UE5 Dedicated Server가 동적으로 CPU 점유율을 60% 성공적으로 줄였다면, 서버 호스팅 환경은 리소스 사용량의 감소를 인식하고 동일한 하드웨어 노드에 새로운 게임 인스턴스를 패킹할 수 있을 만큼 스마트해야 합니다.

이러한 오케스트레이션을 직접 구축하려면 엄청난 양의 DevOps 엔지니어링이 필요합니다. Kubernetes 클러스터를 배포하고, 게임 서버 수명 주기 관리를 위해 Agones를 구성하고, CPU 사용률에 따른 커스텀 스케일링 메트릭을 작성하고, 플레이어를 올바른 인스턴스로 라우팅하기 위해 Load Balancers를 관리해야 합니다. 이는 최소 4-6개월의 전담 인프라 작업이 필요한 일이며, 실제 게임 제작 시간을 직접적으로 뺏게 됩니다.

horizOn을 사용하면 이러한 백엔드 오케스트레이션 서비스가 미리 구성되어 제공됩니다. 이 플랫폼은 동적 인스턴스 패킹, 실시간 서버 부하에 따른 Auto-Scaling, Dedicated Server 빌드를 위한 자동화된 배포 파이프라인을 처리합니다. 전문적인 Backend-as-a-Service에 인프라를 맡김으로써, Kubernetes 매니페스트와 반년 동안 씨름하는 대신 Multiplayer 게임을 출시하는 데 집중할 수 있습니다.

또한 단일 노드에 더 많은 인스턴스를 패킹하면 네트워크 스레드에 영향을 미치는 Noisy Neighbor 문제의 위험이 커집니다. 이러한 병목 현상으로부터 Netcode를 보호하는 것은 매우 중요하며, 이 주제는 The Uefn Server Performance Exploit Explained Hard Armoring Your Unreal Engine Netcode에서 광범위하게 다루고 있습니다.

제로 웨이스트 Multiplayer 아키텍처를 위한 베스트 프랙티스

100인용 Battle Royale을 만들든 지속적인 오픈 월드 서바이벌 게임을 만들든, 하이버네이션 및 제로 웨이스트 기술을 구현하려면 엄격한 아키텍처 규율이 필요합니다. 플레이어 경험을 희생하지 않으면서 서버 Opex를 낮게 유지하기 위한 5가지 검증된 베스트 프랙티스는 다음과 같습니다.

1. Game State를 Tick 루프에서 분리하기

서버 성능의 가장 큰 적은 데이터의 지속적인 폴링(Polling)입니다. 이벤트 발생 여부를 확인하기 위해 절대 Tick()을 사용하지 마세요. 완전히 Event-Driven Architecture로 전환하십시오. 모닥불이 5분 후에 꺼져야 한다면 매 프레임 Tick을 돌려 시간을 빼지 마세요. 300초 후에 정확히 한 번 실행되는 타이머 델리게이트를 설정하십시오. 이를 통해 모닥불 Actor는 4분 59초 동안 완전히 잠든 상태를 유지할 수 있습니다.

2. 공격적인 NetCullDistanceSquared 구현

Unreal Engine은 NetCullDistanceSquared를 기반으로 어떤 Actor를 어떤 클라이언트에 복제할지 결정합니다. 많은 개발자가 이를 기본값으로 두어 서버가 플레이어로부터 수백 미터 떨어진 Actor의 데이터를 직렬화하고 압축하게 만듭니다. 컬링 거리를 점검하십시오. 떨어진 무기는 5,000 유닛(50미터) 이상으로 복제될 필요가 없습니다. Gameplay Loop에 필요한 절대 최소 반경을 계산하고 이를 엄격히 적용하십시오.

3. O(1) 조회를 위해 Spatial Hash Grids 사용

잠들어야 할 Actor를 계산할 때, 엔티티가 100,000개라면 월드의 모든 Actor를 순회(TActorIterator)하는 것 자체가 병목이 됩니다. Spatial Hash Grid를 구현하십시오. Actor가 이동할 때 해시 맵에서 위치를 업데이트합니다. 이를 통해 하이버네이션 매니저는 "그리드 셀 X에 무엇이 있는가?"를 O(1) 시간 복잡도로 쿼리할 수 있어 CPU의 하이버네이션 평가 비용을 사실상 제로로 만들 수 있습니다.

4. 원활한 깨우기를 위해 버퍼 존 활용

플레이어 시야의 경계선에서 바로 섹터를 하이버네이션하지 마세요. 활성 엔티티 주변에 항상 최소 한 개의 그리드 셀 너비의 활성 섹터 "버퍼 존"을 유지하십시오. 그리드 셀 너비가 100미터이고 플레이어가 셀 A에 있다면 인접한 모든 셀(3x3 그리드)은 완전히 활성 상태를 유지해야 합니다. 이렇게 하면 플레이어가 갑자기 경계를 넘어 질주하더라도 목적지 셀이 이미 완전히 초기화되어 Tick하고 있음을 보장합니다.

5. Dedicated Server 빌드를 정기적으로 프로파일링하기

무엇이 CPU를 점유하고 있는지 추측하지 마세요. 시뮬레이션된 부하가 있는 패키징된 Dedicated Server 환경에서 Unreal Insights를 사용하십시오. 특히 GameThread 타이밍을 살펴보세요. 플레이어가 가만히 서 있을 때 PhysicsTickTime이 스레드 그래프를 지배하고 있다면 하이버네이션 로직이 실패하고 있는 것입니다. 텔레메트리는 제로 웨이스트 아키텍처가 이론이 아닌 실제 상황에서 작동하는지 검증할 수 있는 유일한 방법입니다.

서버 Opex의 미래

Fortnite 커뮤니티의 제안은 중요한 진실을 비춥니다. 비싼 Cloud Compute로 서버 성능을 밀어붙이는 현재의 업계 표준은 지속 가능하지 않다는 것입니다. 월드가 커지고 플레이어 수가 증가함에 따라 인프라 비용의 선형적 증가는 라이브 서비스 예산을 서서히 고갈시킬 것입니다.

Sector Physics Hibernation, Logic-Side Culling 및 동적 인스턴스 패킹은 더 이상 AAA 스튜디오만을 위한 최적화가 아닙니다. 모든 규모의 Multiplayer 게임을 위한 생존 요건입니다. 개발 주기 초기에 제로 웨이스트 마인드셋을 채택함으로써 게임의 수익성이 플레이어 기반과 함께 확장되도록 보장할 수 있습니다.

DevOps의 고민 없이 동적 서버 스케일링을 구현할 준비가 되셨다면, horizOn을 무료로 사용해 보거나 API 문서를 확인하여 Multiplayer 인프라가 얼마나 원활하게 통합될 수 있는지 확인해 보십시오.


출처: [Technical Proposal] Unified Operational Sovereignty: Decoupling Opex to Enable a $1.99 V-Buck Economy

이 대시보드는 다음에 의해 애정을 담아 만들어졌습니다 Projectmakers

© 2026 projectmakers.de

unknown-v1.91.1 / unknown-v--