Kubernetes Minecraft
Bell  

마인크래프트 서버 성능 튜닝 방법

안녕하세요, 방울입니다.

제가 어렸을 적부터 다양한 게임 서버를 운영해본 경험을 토대로 게임 서버의 성능을 최대한으로 이끌어내기 위한 팁을 공유하고자 이 포스트를 작성합니다.

해당 포스트는 마인크래프트 자바 에디션의 서버 (Forge, Fabric, Sponge 등 모드 서버 포함)를 기준으로 설명합니다.

서버에 적합한 하드웨어 선택 및 최적화

CPU 코어 클럭 속도가 가능한 최대한 높은 것으로 선정 (성능 영향: 최상)

물리를 사용하는 게임 서버의 대부분은 멀티 스레딩을 통한 부하 분산이 적절히 수행되지 않습니다.

이 때문에 클럭 속도가 느린 코어가 24개 있는 CPU보다 클럭 속도가 빠른 4개의 코어가 있는 CPU가 서버의 부하를 더 잘 처리해줍니다. (서버용 제온 CPU 보다 데스크탑용 I7 또는 I9 등의 고성능 프로세서가 좋습니다)

동시 접속자가 많지 않은 모드 서버 경우에서는 빠른 클럭 3코어 정도면 20 TPS 를 유지할 수 있는 것으로 보입니다.

(Intel CPU) 하이퍼 스레딩 구성 해제 (성능 영향: 상)

위와 동일한 사유로 멀티 스레딩을 통한 성능 개선이 제한적이므로 단일 코어를 최대한으로 사용하는 것이 좋은데, 하이퍼 스레딩이 활성화되어 있을 경우 동일한 코어를 다른 워크로드에서 점유할 수 있는 상태가 되어 연산에 지연이 발생하게 됩니다.

(가상화를 사용할 경우) CPU 코어 어피니티/피닝 설정 (성능 영향: 상)

하나의 베어메탈 장비에 하이퍼바이저 등의 OS를 올려 가상화를 사용할 경우, 가능한 다른 워크로드에 방해받지 않는 게임 서버를 위한 전용 코어를 할당하여 사용하는 것이 좋습니다.

이는 위와 동일한 사유로 단일 스레드의 지속적인 성능 보장이 필요한 상황으로 만약 마크 서버의 메인 스레드가 연산되고 있는 코어에 다른 워크로드가 공유하여 사용할 경우 서버 연산량이 순간적으로 저하되어 접속자들이 순간 적으로 렉이 발생한다고 느낄 수 있습니다.

쿠버네티스 환경에서 서버를 운영할 경우 CPU 매니저 정책을 기본 값인 None 에서 Static 으로 변경한 뒤, Guaranteed QOS 로 설정되도록 리소스 정의를 하면 cgroup 에 의한 코어 배정이 아닌 전용 코어를 배정 받으므로 컨텍스트 스위칭으로 인한 순간적인 서버 틱 저하 현상을 해소할 수 있습니다.

적절한 램 용량 구성 (성능 영향: 중)

서버측은 클라이언트측보다 램 사용량이 덜하긴 하지만, 가용 가능한 메모리 용량이 너무 부족해질 경우 경우에 따라 잦은 스왑이 발생하여 성능 저하가 발생하거나 OOM(Out Of Memory) 에러가 발생하며 서버가 중단될 수 있습니다.

마인크래프트 자바 에디션 서버는 JVM 기반으로 동작하므로, 적당한 힙 사이즈(코드 내에서 동적으로 할당되는 영역)를 지정해주는 것이 좋습니다.

이는 서버 실행 시 JVM에 -Xms16G -Xmx16G 형태의 인수로 전달하여 설정할 수 있으며, 대략적으로 힙 사이즈 용량 * 1.2 한 만큼의 용량이 서버 장비에서 사용 가능한 상태여야 합니다.

또한 마인크래프트 서버에서 사용되는 기본 GC(Garbage Collector)인 G1GC 가 현재 사용되지 않는 램 영역을 청소하면서 STW(Stop The World) 라는 GC 스레드 외 모든 스레드가 일시정지되는 상태가 되는데, 힙 영역을 너무 크게 할당할 경우 정리해야 될 영역이 많아지면서 STW 시간이 길어지게 되고, 이 때 서버에 접속한 플레이어들은 서버의 응답을 바로 받지 못하여 마크 내 어떠한 엔티티에도 상호작용 할 수 없는 상태가 됩니다.

빠른 스토리지 사용 (성능 영향: 중)

마인크래프트의 월드 데이터는 청크라는 단위로 파일시스템에 저장되는데, 이 파일들이 모드 서버 기준 대략 10MB 정도의 용량의 작은 파일로 저장됩니다.

마인크래프트 서버는 접속한 플레이어가 특정 지역으로 이동할 때 해당하는 좌표에 맞는 청크 데이터를 로드하여 클라이언트측에 전송하게 되는데, 이 때 해당 청크 파일을 빠르게 읽을수록 클라이언트에 빠르게 서빙할 수 있으므로 NVMe SSD 와 같은 가능한 빠른 스토리지를 사용하는 것이 좋습니다.

네트워크를 통한 공유 스토리지(iSCSI, NFS 등)를 사용할 경우 빠른 네트워크 속도가 중요합니다.

GC 튜닝 (성능 영향: 하)

일반적인 모드 서버(모드 100개 언저리)의 경우 램 용량을 많이 사용하지 않으나, 수백개의 모드가 포함된 매우 큰 모드팩 서버 가동 시 경우에 따라 GC를 기본 G1GC 에서 ZGC 로 변경해볼 수도 있습니다.

ZGC를 활성화 하는 방법은 다음과 같은 인수를 JVM에 넘겨주면 되며, 필요에 따라 수정해서 사용하면 됩니다.

-XX:ActiveProcessorCount=4 -XX:ParallelGCThreads=4 -XX:ReservedCodeCacheSize=2048M -XX:+UnlockExperimentalVMOptions -XX:+UseNUMA -XX:+UseZGC -XX:ZAllocationSpikeTolerance=95 -XX:ZFragmentationLimit=5 -XX:ZUncommitDelay=2500 -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:TargetSurvivorRatio=80 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1

다음은 위 JVM 옵션의 설명입니다.

-XX:ActiveProcessorCount=4:

  • 이 옵션은 JVM이 사용할 수 있는 논리 프로세서의 수를 강제로 지정합니다. 여기서는 4개의 프로세서를 사용하도록 설정되어 있습니다. 이는 애플리케이션이 실행될 때 사용 가능한 프로세서를 줄이거나 늘려서 성능을 조정할 수 있습니다.

-XX:ParallelGCThreads=4:

  • 병렬 가비지 컬렉션 시 사용될 쓰레드의 수를 지정합니다. 여기서는 4개의 쓰레드가 가비지 컬렉션을 수행하도록 설정되어 있습니다. 이 값은 JVM이 얼마나 많은 CPU 리소스를 가비지 컬렉션에 할당할지를 결정합니다.

-XX:ReservedCodeCacheSize=2048M:

  • 코드 캐시의 최대 크기를 지정합니다. 여기서는 2048MB로 설정되어 있으며, Just-In-Time (JIT) 컴파일러가 컴파일한 코드와 같은 캐시된 코드의 공간을 지정합니다. 큰 코드 캐시는 컴파일된 코드를 더 많이 저장할 수 있어 성능이 향상될 수 있습니다.

-XX:+UnlockExperimentalVMOptions:

  • 실험적인 VM 옵션을 사용할 수 있게 해줍니다. 이 옵션을 설정하지 않으면 JVM은 실험적인 옵션들을 인식하지 못합니다. 이는 특정 성능 향상 기능이나 새롭게 추가된 기능들을 테스트할 때 유용합니다.

-XX:+UseNUMA:

  • Non-Uniform Memory Access (NUMA) 시스템에서 메모리 관리를 최적화합니다. NUMA 시스템은 메모리 접근 시간이 메모리의 물리적 위치에 따라 다를 수 있기 때문에, 이 옵션은 메모리 접근 시간을 줄여 성능을 향상시킬 수 있습니다.

-XX:+UseZGC:

  • Z Garbage Collector (ZGC)를 사용하도록 지정합니다. ZGC는 대규모 힙 메모리를 빠르게 관리할 수 있는 가비지 컬렉터로, 매우 낮은 가비지 컬렉션 지연 시간 (pause time)을 목표로 합니다.

-XX:ZAllocationSpikeTolerance=95:

  • ZGC가 단기적으로 할당할 수 있는 메모리의 스파이크를 허용하는 비율을 설정합니다. 여기서는 95%로 설정되어 있어, ZGC가 갑작스러운 메모리 할당 증가를 어떻게 다룰지를 결정하는 데 영향을 줍니다.

-XX:ZFragmentationLimit=5:

  • ZGC에서 메모리 단편화를 관리하기 위한 제한을 설정합니다. 5%로 설정되어 있으며, 메모리의 단편화가 이 비율을 초과할 경우 ZGC는 단편화를 줄이기 위해 추가적인 작업을 수행할 수 있습니다.

-XX:ZUncommitDelay=2500:

  • ZGC에서 사용되지 않는 메모리를 다시 시스템에 반환하는 데 걸리는 지연 시간을 밀리초 단위로 설정합니다. 여기서는 2500ms (2.5초)로 설정되어 있습니다. 이는 메모리 사용 패턴에 따라 메모리 반환 시점을 지연시켜 성능을 최적화할 수 있습니다.

-XX:+ParallelRefProcEnabled:

  • 참조 처리(Reference Processing)를 병렬로 수행하도록 허용합니다. 이 옵션은 가비지 컬렉션 중에 참조 객체 (약한 참조, 강한 참조 등)의 처리를 병렬로 수행하여 성능을 향상시킬 수 있습니다.

-XX:MaxGCPauseMillis=200:

  • 가비지 컬렉션 중의 최대 일시 정지 시간을 밀리초 단위로 설정합니다. 여기서는 200ms로 설정되어 있으며, 이 값에 맞추어 가비지 컬렉션이 최적화되도록 합니다. 주로 낮은 지연 시간이 필요한 애플리케이션에서 사용됩니다.

-XX:+DisableExplicitGC:

  • 명시적인 가비지 컬렉션 호출 (System.gc())을 무시합니다. 이는 애플리케이션이 임의로 가비지 컬렉션을 호출하지 못하게 하여, JVM 자체의 가비지 컬렉션 스케줄링이 방해받지 않도록 합니다.

-XX:+AlwaysPreTouch:

  • JVM이 애플리케이션의 힙을 초기화할 때, 모든 메모리 페이지를 미리 터치(pre-touch)하여 할당된 모든 메모리 페이지가 시스템의 실제 메모리에 매핑되도록 합니다. 이는 페이지 오류(page fault)를 줄여 성능을 향상시킬 수 있습니다.

-XX:TargetSurvivorRatio=80:

  • Eden 영역에서 Survivor 영역으로 이동하는 객체의 비율을 설정합니다. 80%로 설정되어 있으며, 이는 Survivor 영역이 전체 Young Generation 메모리의 80%를 차지하도록 목표 비율을 설정합니다. 이는 가비지 컬렉션이 효율적으로 이루어지도록 도와줍니다.

-XX:+PerfDisableSharedMem:

  • 성능 모니터링을 위한 공유 메모리를 비활성화합니다. 이는 성능 모니터링 기능을 사용하지 않도록 하여 메모리와 성능을 절약할 수 있습니다.

-XX:MaxTenuringThreshold=1:

  • 객체가 Young Generation에서 Old Generation으로 이동하기 전에 Survivor 영역에 남아 있을 수 있는 최대 GC 사이클 수를 설정합니다. 여기서는 1로 설정되어 있어, 한 번의 GC 사이클 후에 객체가 Old Generation으로 이동합니다. 이는 Young Generation에서 빠르게 Old Generation으로 이동시키려는 설정입니다.

네트워크 속도 (성능 영향: 하)

마인크래프트 게임 서버에서 네트워크 최대 속도는 성능에 큰 영향을 끼치지 않습니다.

게임 서버는 파일 전송과는 달리 네트워크 대역폭을 거의 소모하지 않으므로 적절한 수준의 속도와 안정적인 연결, 낮은 지연시간(핑 속도)만 유지 되면 됩니다.

단, 요즘 인터넷 방송을 볼 때 많이 보이는 인게임에서 주변 플레이어와 음성으로 소통하는 VoiceChat 모드와 같이 음성 데이터를 다루는 모드를 포함하여 서버를 운영할 경우 동시 사용자에 따라 네트워크 대역폭을 많이 소진할 수 있고 빠른 응답성이 필요하므로 주의가 필요합니다.

게임 서버 최적화

청크 사전 생성 (성능 영향: 상)

모드 서버의 경우 신규 청크 생성 시 월드에 추가하는 블럭 등 여러 프로세스가 동작하다 보니 많은 성능을 요구합니다.

만약 월드 탐험이 메인이 되는 컨텐츠라면 신규 청크 생성 시 서버 CPU 성능에 따라 심한 지연이 발생할 수 있습니다.

걸어서 신규 청크 확장을 할 경우는 어느정도 버틸 수 있지만, 모드 서버의 경우 보통 날아서 청크 확장을 하는 경우가 많다보니 여러 청크를 순간적으로 생성하려고 하는 시점에 서버의 연산이 지연되어 TPS가 급락하는 현상이 발생하게 됩니다.

이를 사전에 어느정도 대비하기 위해 청크 프리제네레이터와 같은 모드를 사용하여 공개적으로 서버를 오픈하기 전 플레이어들이 탐험할 것으로 예측되는 주변 청크들을 미리 파일로 생성해두면 신규 청크 확장으로 인한 서버 일시 중단 현상이 대폭 해소됩니다.

저의 경우에는 서버 공식 오픈 직전 또는 접속자가 없는 시간을 이용해 스폰 지점을 중심으로 반경 3천~5천 블럭 거리를 미리 생성하여 스폰 지점 중심 인근을 탐험할 경우 신규 청크 생성에 따른 서버 일시 정지 현상을 해소합니다.

View Distance 수치 축소 (성능 영향: 상)

여러 설정을 통해서도 TPS 수치가 좀처럼 향상되지 않는다면, 서버 설정에서 View Distance 수치를 낮춰 플레이어 별로 제공되는 청크 개수를 줄여서 서버측에서 동시에 로드되는 청크 개수를 줄여볼 수 있습니다.

단, 이 값을 너무 낮추게 되면 클라이언트 측에서 렌더링 되는 거리가 짧아져 먼 곳을 볼 수 없어 게임 플레이에 영향을 끼칠 수 있습니다.

청크 내 엔티티류 개수 제한 (성능 영향: 상)

마인크래프트 서버 성능에 영향을 끼치는 제일 큰 유형은 엔티티(동물, 몬스터 등 움직이는 개체), 타일엔티티(기계 블럭 등의 특정 좌표에 고정된 블럭이지만 내부적으로 연산이 필요한 개체)가 있습니다.

이 엔티티들이 단일 청크 또는 여러 청크에 걸쳐서 다수 활성된 상태일 경우 서버의 리소스를 많이 잡아먹게 됩니다.

서버 성능을 위해 플레이어들에게 일정량 이상의 엔티티를 생성하지 못하도록 제한할 경우 서버 성능을 보장할 수 있습니다.

(돌이나 유리, 물 같은 아무 기능이 없는 일반 블럭류는 타일엔티티가 아니며, 이와 같은 일반 블럭은 다수 설치하더라도 클라이언트 측 성능에 따라 프레임 드랍을 발생시킬지언정 서버 성능에는 전혀 영향을 끼치지 않습니다.)

(동시 접속자가 많을 경우) 번지코드 등의 다중 서버 아키텍처 구성 (성능 영향: 상)

일정 수준 이상 동시 접속자가 발생할 경우, 마인크래프트 서버 최적화 상태 상 아무리 빠른 코어의 CPU 를 사용하더라도 서버 TPS 저하를 피해갈 수 없습니다.

이를 해결하기 위해서는 번지코드 등을 통하여 컨텐츠별로 서버를 분리하여 단일 서버에 동시 접속되는 플레이어를 줄여 로드밸런싱을 하는 것이 좋습니다.

서버 사용 현황 모니터링 시스템 구축

성능 향상에 앞서 제일 중요한 것은 현재 물리 서버의 가용 자원에 대한 모니터링과, 게임 서버가 사용하는 리소스 현황 등에 대한 메트릭을 확보 및 시각화하여 실질적으로 성능에 영향이 되는 원인을 파악한 후 그에 맞는 설정을 하는 것입니다.

이는 현재 인프라 구성 현황 등에 따라 다르게 구성할 수 있으며, 저와 같은 경우는 다음과 같은 형태로 서버의 상태를 모니터링하며 운영하고 있습니다.

아래 예시는 이전에 캡처했던 이미지이며 동일한 시점이 아닙니다.

여기까지 제 경험 상 마인크래프트 모드 서버 성능을 향상시키기 위해 일반적으로 검토해야 할 항목들을 정리해보았습니다.

읽어주셔서 감사합니다.

Views: 37

댓글 남기기