PART 6 - 강의 3/3

대규모 서버 확장성

100+ 플레이어 지원을 위한 서버 최적화 전략

01

서버 병목점 분석

대규모 서버의 주요 제한 요소

병목점 분석 ┌─────────────────────────────────────────────────────────────┐ │ 서버 병목점 계층 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ [1] CPU 틱 시간 │ │ └─ 모든 Actor의 Tick() 호출 │ │ └─ Physics 시뮬레이션 │ │ └─ AI/Behavior Tree │ │ │ │ [2] 네트워크 대역폭 │ │ └─ 리플리케이션 데이터 (N^2 문제) │ │ └─ RPC 빈도 │ │ │ │ [3] 메모리 │ │ └─ 플레이어당 Actor/Component │ │ └─ 리플리케이션 버퍼 │ │ │ └─────────────────────────────────────────────────────────────┘ 64명 서버 기준: - Actor 수: ~5000개 - 초당 리플리케이션: ~200,000 프로퍼티 - 대역폭: 10-50 Mbps (업링크)
02

서버 틱 최적화

틱 빈도와 분산 처리

C++ // 틱 그룹과 간격 설정 AMyActor::AMyActor() { // 틱 빈도 낮추기 PrimaryActorTick.TickInterval = 0.1f; // 10Hz // 틱 그룹 설정 (물리 후 실행) PrimaryActorTick.TickGroup = TG_PostPhysics; // 서버에서만 틱 PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = false; } void AMyActor::BeginPlay() { Super::BeginPlay(); // 서버에서만 틱 활성화 if (HasAuthority()) { SetActorTickEnabled(true); } } // 틱 분산 - 프레임별로 다른 Actor 그룹 처리 void AMyActor::Tick(float DeltaTime) { static int32 FrameCounter = 0; FrameCounter++; // Actor ID 기반 분산 (4프레임 중 1프레임만 처리) if ((GetUniqueID() % 4) != (FrameCounter % 4)) return; // 실제 로직 UpdateAI(); }
SignificantActors

UE5의 Significance Manager를 사용하면 플레이어와의 거리에 따라 틱 빈도를 자동으로 조절할 수 있습니다.

03

리플리케이션 최적화

대역폭 절감 전략

C++ // 1. Net Update Frequency 조절 AMyCharacter::AMyCharacter() { // 초당 네트워크 업데이트 횟수 NetUpdateFrequency = 30.f; // 중요 캐릭터 MinNetUpdateFrequency = 10.f; // 최소 빈도 } // 2. Dormancy 활용 void AMyActor::SetDormancy() { // 변경 없는 Actor는 복제 중단 SetNetDormancy(DORM_DormantAll); } void AMyActor::WakeFromDormancy() { // 변경 시 깨우기 FlushNetDormancy(); } // 3. Relevancy 커스터마이징 bool AMyActor::IsNetRelevantFor( const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const { // 거리 기반 Relevancy float DistSq = (GetActorLocation() - SrcLocation).SizeSquared(); // 중요도에 따라 거리 조절 float MaxRelevantDist = bImportant ? 50000.f : 15000.f; return DistSq < FMath::Square(MaxRelevantDist); } // 4. 조건부 리플리케이션 void AMyActor::GetLifetimeReplicatedProps(...) const { // 오너에게만 복제 (인벤토리 등) DOREPLIFETIME_CONDITION(AMyActor, Inventory, COND_OwnerOnly); // 초기화 시에만 복제 (변경 없는 설정) DOREPLIFETIME_CONDITION(AMyActor, CharacterClass, COND_InitialOnly); }
04

서버 인스턴스 분산

수평 확장 아키텍처

아키텍처 ┌─────────────────────────────────────────────────────────────┐ │ MMO 스타일 아키텍처 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ [Login Server] │ │ │ │ │ v │ │ [Matchmaker / Zone Manager] │ │ │ │ │ ┌─────┼─────┬─────────┐ │ │ v v v v │ │ [Zone1] [Zone2] [Zone3] [Zone4] <- 지역별 서버 │ │ │ │ │ │ │ │ └───────┴───────┴───────┘ │ │ │ │ │ [Database Cluster] │ │ │ └─────────────────────────────────────────────────────────────┘ 각 Zone 서버: 64-100명 수용 Zone 간 이동: Server Travel 또는 재접속
C++ // Zone 전환 요청 void AMyPlayerController::RequestZoneTransfer(FName TargetZone) { // 서버에 존 전환 요청 ServerRequestZoneTransfer(TargetZone); } UFUNCTION(Server, Reliable) void AMyPlayerController::ServerRequestZoneTransfer(FName TargetZone) { // Zone Manager에 전환 요청 UMyGameInstance* GI = GetGameInstance<UMyGameInstance>(); // 플레이어 데이터 저장 GI->SavePlayerData(this); // 새 Zone 서버 주소 획득 (외부 서비스) FString NewServerAddress = GI->GetZoneServerAddress(TargetZone); // 클라이언트에게 새 서버 접속 지시 ClientTravel(NewServerAddress, TRAVEL_Absolute); }
SUMMARY

핵심 요약

  • 병목점 - CPU 틱, 네트워크 대역폭, 메모리가 주요 제한 요소
  • 틱 최적화 - 틱 간격 조절, 서버 전용 틱, 분산 처리
  • 리플리케이션 최적화 - Dormancy, Relevancy, 조건부 복제
  • 수평 확장 - Zone 기반 서버 분산으로 대규모 플레이어 수용
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 서버 성능 프로파일링

stat net, stat game, stat repgraph 명령어로 서버 성능을 측정하세요. 50명 봇을 추가하며 서버 FPS 저하 패턴을 분석하고 병목 지점을 찾아보세요.

실습 2: 복제 빈도 최적화

AActor::NetUpdateFrequency와 MinNetUpdateFrequency를 클래스별로 설정하세요. NPC는 10Hz, 프로젝타일은 60Hz, 환경 오브젝트는 1Hz로 설정하고 대역폭 변화를 측정하세요.

심화 과제

플레이어 수에 따라 동적으로 NetUpdateFrequency와 CullDistance를 조정하는 적응형 서버 스케일링 시스템을 구현하세요. 서버 FPS가 30 이하로 떨어지면 자동으로 복제 빈도를 줄이는 로직을 구현하세요.