이번 개발일지에서는 서버 성능을 개선하기 위해 저희가 했던 노력과 그 결과를 여러분들과 같이 공유하고자 합니다.
서버 성능이 왜 중요한지에 대해서는 깊이 설명하지 않아도 많은 플레이어 분들이 이미 알고 계실거라 생각합니다. 배틀그라운드가 사용하는 언리얼 엔진은 클라이언트-서버 모델에 기초하기 때문에 모든 클라이언트에 각 'Actor'들에 대한 상태 정보가 서버를 통해 업데이트 되어야 합니다.
※Actor: 언리얼 엔진에서 사용되는 개념으로, 캐릭터, 건물, 배경, 플레이어의 관점에서 맵을 보여주기 위해 사용 되는 카메라 등 게임 내 레벨 안에 배치할 수 있는 오브젝트를 말합니다.
서버 성능은 보통 서버의 FPS(Frame Per Second 혹은 frame-rate, tick-rate)로 측정하게 되는데요. 서버의 성능이 높아지면 한 프레임당 시간이 줄어들게 되고, 그 시간이 줄어들 수록 서버의 반응성은 높아집니다.
서버의 반응성은 다른 말로 “네트워크 딜레이”라고도 표현하는데요. 이 반응성이 높아질 수록 (또는 네트워크 딜레이가 줄어들 수록) 더 매끄럽고 쾌적한 게임 경험을 제공할 수 있습니다. 왜냐하면 서버가 빨리 응답할 수록 “내가 한 행동이 다른 사람에게 보여지는 시간”이 줄어들게 되기 때문입니다. 예를 들자면 내가 발사한 총이 다른 플레이어들의 화면에 좀 더 빠르게 보여지게 되는 것입니다. 플레이어 여러분들이 흔히 얘기하는 순단 현상을 줄이려면 서버의 응답 시간을 줄여야 하는 것입니다.
PC 1.0 버전 패치노트#14 적용을 통한 개선사항
PC 1.0 버전 패치노트#14 적용 전 네트워크 처리 구조와 시사점
PC 1.0 버전 패치노트#14 적용 전 기존의 언리얼 엔진은 서버에서 아래와 같은 흐름으로 네트워크 처리가 이루어졌습니다.
위 네트워크 처리 흐름을 먼저 설명 드리겠습니다. “Net Dispatch” 단계에서는 클라이언트에서 수신된 패킷이 서버에서 처리됩니다. 이 단계에서는 총기 격발이나 이동과 같은 내용들이 처리됩니다. 이 때 처리된 내용들은 효율, 적합성에 따라 RPC(Remote Procedure Calls)와 Replication, 이 2가지 중 한 가지 형태로 다른 클라이언트에 전파됩니다. 이후 물리 시뮬레이션과 같은 서버에서의 게임 로직 처리가 “Simulate & Render” 단계에서 이뤄지며, 그 처리 결과는 “Net Flush” 단계를 통해 모든 클라이언트에 전달됩니다.
위 구조상에서는 “Net Dispatch” 단계에서 처리된 RPC가 바로 전송되지 않고 Buffer 대기열에 진입하게 됩니다. 이후, “Net Flush” 단계에서 현재 Buffer에 저장되어 있는 내용이 모든 클라이언트에 전송되고 Buffer는 비워집니다.
이 구조를 그대로 따르게 되면 “Net Dispatch”에서 “Net Flush”에 가기 전에 “Simulate & Render” 단계를 거쳐야 RPC가 전송되기 때문에 그만큼 딜레이가 발생합니다. 언리얼에서 이렇게 만든 이유는 UDP로 전달되는 패킷의 숫자를 최소화하기 위해서라고 생각됩니다. 패킷 숫자가 최소화되어야 네트워크를 더 효율적으로 활용할 수 있기 때문입니다.
변경된 네트워크 처리 구조와 개선점
그러나 패킷 숫자를 줄이는 것보다 딜레이를 줄여서 반응성을 높이는 것이 저희 배틀그라운드에는 더 중요하다고 판단하여 PC 1.0 버전 패치노트#14에서는 아래와 같이 구조를 변경했습니다. “Simulate & Render” 전에 “Net Send Flush”라는 단계를 추가한 것입니다.
“Net Send Flush” 단계에서는 Buffer에 저장되어 있던 UDP 데이터가 모두 전송되고, 비워집니다. 이를 통해 총 딜레이 시간이 “Simulate & Render”에 소요되던 시간만큼 감소하게 된 것입니다. “Net Send Flush” 단계에서는 그동안 대기열에 진입해있던 있던 데이터가 처리됩니다.
이러한 네트워크 처리 구조 변경을 통해 “Net Send Flush”, “Net Flush” 총 2번의 네트워크 업데이트가 전송되면서 지난 PC 1.0 버전 패치노트#14부터 Network Update Rate가 2배 증가하게 되었습니다. (이 때문에 서버 tick-rate가 2배 증가한 것 아니냐는 추측이 있었지만, 그게 아니라 서버 frame 처리 중간에 Network Update가 한 번 더 전송되면서 Network Update Rate가 60 tick-rate가 되었던 것입니다.)
이 결과는 유튜버 Battle(non)sense의 지난 PC 1.0 버전 패치노트#14 관련 영상에서도 확인할 수 있습니다. 아래 표를 보면, 40명의 플레이어가 생존해있을 때 Gunfire(총기 격발)의 평균 딜레이가 94.5msec에서 77msec로 18% 감소되었습니다.
PC 1.0 버전 패치노트#19 적용을 통한 개선사항
PC 1.0 버전 패치노트#19 적용 전 프로파일링 결과, 그리고 새로운 접근법
PC 1.0 버전 패치노트#19 적용 전인 2018년 6월 25일, 90명의 플레이어가 생존해있을 때 측정한 프로파일 정보는 아래와 같습니다.
Function
Value
Total frame time
106.4 msec
Tick-rate
9.3
Net Dispatch
41.8 msec
Simulate & Render
18.9 msec
Net Flush
43.2 msec
“Net Flush” 단계에 소요되는 시간이 43.2msec로 전체 frame 시간의 41%임을 알 수 있습니다. 이 시간의 상당 부분은 각각의 Actor를 클라이언트에 replication 시키기 위해서 “Serialize“ 하는 데 소요되는 시간입니다.
※ Serialize: 네트워크를 통해 Actor들의 상태를 클라이언트에 전달할 수 있도록 이 데이터를 메모리에 순서대로 쓰는 과정
위 프로파일링 결과를 바탕으로 최적화 방법을 고민하던 중, 이런 생각을 하게 되었습니다.
“Replication 되는 Actor(특히 Character)의 숫자를 줄일 수 있다면, 전체 Net Flush 시간이 많이 줄어들 수 있다.”
저희 배틀그라운드는 언리얼 엔진으로 설계된 서버를 사용하는 다른 게임보다 몇 배나 많은 최대 100명의 사용자가 같이 플레이를 하기 때문에 Actor의 개수가 많을 수밖에 없습니다. Actor가 가진 데이터가 많은 것도 문제지만, 더 큰 문제는 Actor의 개수가 많다는 것이었습니다. 그래서 총 Actor 숫자를 줄이기 위한 방법을 고민하던 중, 먼 거리에 있는 캐릭터들을 더 낮은 빈도로 replication 하면 어떨까 하는 생각을 하게 되었습니다.
먼 거리에 있는 캐릭터들에 변화를 주는 것이라 게임 플레이에는 영향을 주지 않으면서도 serialize 되는 Actor의 숫자는 많이 줄이고, 결과적으로 Net Flush 소요 시간도 줄일 수 있을 테니까요.
개발 과정: Replication Interleaving 시스템
위의 생각에서 출발해 다다른 결론은 클라이언트와 Actor의 거리에 따라서 replication 요청을 적절한 빈도로 skip하는 형태의 시스템을 구현해 보자는 것이었습니다. 그래서 이 시스템의 이름은 “Replication Interleaving”이라고 지었습니다.먼저 강제로 Actor가 replication 되는 구간을 분리해, 그 안에서 먼 거리에 있는 캐릭터들의 replication 빈도를 낮췄을 때 어떤 문제가 발생하는지, 시각적으로 어떤 변화가 나타나는지를 분석했습니다.
이후 replication 빈도를 줄였을 때 발생하는 문제를 해결했고, 기존 replication 빈도의 4분의 1 수준까지 줄여도 게임 플레이에 큰 영향이 없다는 결론을 내렸습니다.
완성된 Replication Interleaving 시스템은 아래와 같이 구현되었습니다:
•거리에 따라서 몇 프레임 동안 replication을 skip 할지 결정한다.
•3단계까지 설정 가능하며, 1단계는 1 frame을 skip 하고, 2단계는 2 frame을 skip, 3단계는 3 frame을 skip 한다.
위 시스템을 구현한 뒤, 단계별 적절한 거리 값을 찾기 위해 QA팀에서 관련 테스트를 진행했습니다. 3 frame을 skip 해보니 캐릭터의 움직임이 튀는 듯 보이는 현상이 생겨서 테스트 후 3 단계는 사용하지 않기로 결정했습니다. 현재 단계별 적용된 값은 아래와 같습니다.
•1단계: 70m 이상의 거리에 있는 캐릭터에 대해서 1 frame skip
•2단계: 400m 이상의 거리에 있는 캐릭터에 대해서 2 frame skip
o 참고: 위 내용은 현재 적용된 내용이며, 향후 서버 성능을 더 개선하거나 움직임을 더 부드럽게 보이게 하기 위해 값을 변경할 수도 있습니다.
개선 결과
위 시스템을 적용한 이후 서버 성능이 20% 향상되었습니다. 아래는 NA 지역에 있는 서버에서 85명이 생존해있을 때 tick-rate를 추적한 결과입니다. 이 업데이트를 통해 18.5였던 서버 tick-rate가 22.9로 약 22% 향상되었습니다. NA 지역뿐만 아니라 다른 지역에서도 평균 20% 이상의 tick-rate 향상이 측정되었습니다.
이보다 더 고무적인 것은 반응 시간의 변화입니다. 참고 자료로 유튜버 Battle(non)Sense의 PC 1.0 버전 패치노트#19 관련 영상 내 자료를 인용합니다.
위 표를 보면 85명 이상이 생존해있을 때 GunFire의 평균 딜레이가 149.4msec에서 61.6msec으로 58% 감소한 것을 확인할 수 있습니다. 이는 게임 초반에 발생하던 소위 순단현상이 상당히 개선되었다는 것을 의미합니다.
Replication Interleaving뿐만 아니라 다른 개선 사항들을 통해 80명 이상의 플레이어가 생존한 상태에서 서버 tick-rate가 20% 상승했고, Network Delay는 50% 줄었습니다.
마치며
배틀그라운드가 서비스를 시작한 직후부터 서버 tick-rate를 개선하기 위한 노력은 수차례 있었습니다. 소프트웨어 측면의 문제뿐만 아니라 하드웨어에 대한 개선도 진행된 바 있습니다. 하지만 PC 1.0 버전 패치노트#19 적용 이전의 몇 개월간은 플레이어 여러분들이 확실히 체감할 수 있는 개선 사항이 많지 않았던 것이 사실입니다.
이번 FIX PUBG기간 동안 서버 성능 개선 작업의 우선순위를 상당히 많이 높였습니다. 이를 위해 여러 아이디어에 대한 연구와 테스트가 이뤄지고 있습니다.
단 하나의 기능을 구현하기 위해서는 사전 연구가 이뤄져야 하고, 기능이 구현된 뒤에는 수차례의 분석 및 검증, 테스트 과정이 필요합니다. 이렇게 한 가지를 개선하는 데에도 노력과 시간이 꾸준히 투자되어야 하기에 단시간에 모든 문제를 해결하기는 어렵습니다. 새로운 것을 구현하다 잘못하면 더 큰 문제를 초래할 수도 있습니다. 그 때문에 최대한 안전하게 새로운 기능을 구현하고 적용해야 한다는 점도 있습니다.
위 개선 사항 적용 이후, 현재는 “Net Dispatch” 단계의 최적화를 연구하고 있습니다. 분석 결과, 대부분의 시간이 캐릭터 이동 처리에 소요되고 있으며, 이를 최적화 할 수 있는 가능성을 확인한 상태입니다. 캐릭터의 이동이 배틀그라운드에서는 게임 플레이에 많은 영향을 미치는 만큼 최적화 작업 때문에 캐릭터의 이동이 이상하게 나타나는 일이 없도록 신중한 작업이 필요합니다.
최적화를 위해 몇 가지 아이디어를 테스트 중에 있으며, 테스트 후 이 아이디어들이 적용된다면 현재 41.8msec인 “Net Dispatch” 소요 시간이 50% 이하로 줄어들 것으로 기대하고 있습니다. 이 내용을 적용하여 안정화가 될 때까지는 약 1개월 이상의 작업이 필요할 것으로 예상됩니다. 최선을 다해 빠르게 완성하여 적용할 수 있도록 노력하겠습니다.
저희의 현재 목표는 게임 시작부터 종료 시점까지 서버 tick-rate가 항상 30선에서 유지되는 것입니다. 이 목표를 이루고, 더 나은 배틀 로얄 경험을 제공하기 위해 끊임없이 노력할 것입니다.
감사합니다.
김상균 드림
Head of Development, PUBG Amsterdam
------------------
서울 본사와 암스테르담, 위스콘신 등에서 배틀그라운드 개발팀과 함께 일할 엔지니어들을 찾고 있습니다. 배틀그라운드에 대한 열정과 관심, 풍부한 언리얼 엔진 활용 개발 경험을 갖춘 인재들의 지원을 기다립니다! 자세한 내용은 아래 링크에서 확인하세요.