멀티플레이어 서버 설계
온라인 게임 서버를 어떻게 나누고 띄우고 늘리는가. 권한 서버, 전용 서버 할당, 세션형과 지속 세계, 지역과 지연을 다룬다.
목차 (8)
연결 구조 넷: 누가 판정하고 누가 중계하나
온라인 게임을 설계할 때 가장 먼저 정하는 것은 "누가 진실을 갖는가"다. 두 사람이 같은 순간 같은 상자를 집으면 누가 가졌는지, 총알이 맞았는지를 한 곳에서 판정해야 화면이 어긋나지 않는다. 판정하는 쪽과 패킷이 지나가는 길을 어떻게 놓느냐에 따라 구조가 넷으로 나뉜다. 화면이 지연을 어떻게 숨기는지(클라이언트 예측, 랙 보상, 롤백)는 게임 개발의 멀티플레이 노트에 있으니, 여기서는 그 밑의 서버 구조를 다룬다.
- 클라이언트-서버(권한 서버): 회사가 띄운 전용 서버(dedicated server)가 게임 상태의 유일한 원본이다. 클라이언트는 입력만 보내고 서버가 결과를 정해 돌려준다. 클라이언트를 고쳐도 서버가 받아 주지 않으니 치트에 강하고, 누가 나가도 경기가 이어진다. 대가는 서버 비용과 운영이다. 경쟁 FPS·배틀로얄·MOBA 대부분이 이 구조다.
- P2P: 서버 없이 플레이어끼리 직접 주고받는다. 서버비가 들지 않고 가까운 두 사람은 지연이 가장 짧다. 그러나 판정할 곳이 없어 모두가 같은 계산을 하는 결정적 락스텝이나 롤백을 써야 하고, 연결 수가 사람 수의 제곱으로 늘며, 상대에게 내 IP가 드러난다. 격투 게임과 소규모 협동 게임이 주로 쓴다.
- 리슨 서버: 플레이어 하나(호스트)가 게임을 하면서 서버 역할도 한다. 서버비는 없지만 호스트가 지연 0으로 유리하고, 호스트가 치트를 쓰면 막을 수 없으며, 호스트가 나가면 경기가 멈춘다(아래 "호스트 이전"). 친구끼리 하는 협동 게임에 흔하다.
- 릴레이: P2P나 리슨 서버의 패킷을 중계 서버가 대신 전달한다. 판정은 하지 않고 길만 놓는다. NAT 뒤라 직접 연결이 안 되는 사람도 이어 주고, 서로의 IP를 숨겨 디도스를 막는다. Valve의 Steam Datagram Relay 문서는 P2P와 전용 서버 트래픽을 Valve의 중계망으로 싣고 IP를 드러내지 않는다고 설명한다. Unity도 전용 서버 없이 플레이어를 잇는 Relay를 Multiplayer Services SDK 문서에서 Lobby·Matchmaker와 함께 묶어 제공한다.
실제 게임은 섞어 쓴다. 판정은 전용 서버가 하되 음성 채팅만 P2P로 보내거나, 협동 게임을 리슨 서버로 돌리면서 연결은 릴레이를 거치게 하는 식이다. 이 노트의 나머지는 가장 손이 많이 가는 전용 서버 구조를 중심으로 본다.
전용 서버의 한살이: 플릿, Ready, 할당, Shutdown
세션형 게임의 전용 서버는 웹 서버와 성격이 다르다. 웹 서버는 요청 하나를 아무 서버나 처리해도 되지만, 게임 서버 프로세스 하나는 경기 하나의 상태를 메모리에 들고 수십 분 동안 그 경기만 돌린다. 경기 도중 이 프로세스를 죽이면 열 명의 경기가 날아간다. 그래서 일반 오토스케일러처럼 "부하가 줄면 아무 서버나 끈다"가 통하지 않고, 서버마다 지금 경기 중인지를 아는 전용 관리 계층이 필요하다.
쿠버네티스 위에서 이 일을 하는 오픈 소스가 Agones이고, AWS의 관리형 서비스가 Amazon GameLift Servers다(2025년에 Amazon GameLift에서 이름이 바뀌었다). 둘은 용어만 다르고 같은 고리를 돈다. Agones 클라이언트 SDK 문서는 서버가 SDK로 Ready·Allocate·Shutdown을 알리고 Health 신호가 끊기면 Unhealthy가 된다고 적는다. GameLift Servers의 동작 설명도 서버 프로세스가 준비를 알리고, 배치 시스템이 경기를 맡기면 세션을 시작하고, 끝나면 스스로 내려가는 흐름을 그린다.
플릿이 같은 서버 빌드를 4개 띄운다. 프로세스가 맵과 에셋을 읽는 동안은 아직 손님을 받을 수 없다.
- 플릿(fleet): 같은 서버 빌드를 돌리는 서버 묶음이다. 새 빌드를 올리면 새 플릿을 띄우고, 옛 플릿은 새 할당을 받지 않은 채 경기 중인 서버가 다 끝나면 사라진다. 경기를 끊지 않는 배포다.
- 할당(allocation): 매치메이커가 "서버 하나 달라"고 하면 Ready인 서버 하나를 골라 Allocated로 바꾸고 주소를 돌려준다. 여러 매치메이커가 동시에 같은 서버를 집지 않도록 이 전환은 원자적이어야 한다. Allocated 서버는 축소나 빌드 교체 때도 지우지 않는다.
- 한 프로세스 한 경기: 경기가 끝나면 서버를 재사용하기보다 지우고 새로 띄우는 편이 흔하다. 앞 경기의 남은 상태나 메모리 누수가 다음 경기로 넘어가지 않는다. 서버 기동이 느린 게임은 Ready로 되돌려 재사용하기도 한다.
- 기기 하나에 여러 프로세스: 서버 프로세스 하나가 코어 하나도 다 쓰지 않는 게임이 많아, 기기 한 대에 프로세스를 여러 개 띄워 포트로 나눈다. 몇 개를 올릴지는 틱당 CPU 시간을 재서 정한다.
세션형과 지속 세계: 샤드, 구역, 인스턴스
경기가 끝나면 버리는 세션형 서버와 달리, MMO의 지속 세계(persistent world)는 서버가 꺼져도 세계가 남아야 한다. 캐릭터, 아이템, 땅에 떨어진 물건, 길드의 성이 데이터베이스에 저장되고, 수천~수만 명이 한 세계를 함께 쓴다. 서버 한 대로는 감당이 안 되니 세계를 나누는데, 나누는 축이 셋이다.
- 샤드(shard, 렐름·월드·서버라고도 부른다): 같은 세계를 통째로 여러 벌 띄우고 사람을 나눠 담는다. 샤드끼리는 데이터베이스까지 따로라 서로 만나지 못한다. 가장 쉽게 늘리는 법이지만 친구가 다른 샤드에 있으면 함께 놀 수 없고, 인구가 줄면 샤드를 합쳐야 한다.
- 구역(zone): 한 샤드의 지도를 땅 단위로 잘라 서버마다 맡긴다. 플레이어가 경계를 넘으면 지금 서버가 캐릭터 상태를 다음 서버에 넘기는 핸드오프를 한다. 로딩 화면으로 끊으면 쉽고, 끊김 없이(seamless) 넘기려면 경계 양쪽 서버가 서로의 근처 개체를 보여 주고 넘기는 순간의 공격·이동을 맞춰야 해 어렵다.
- 인스턴스(instance): 던전이나 전장처럼 정해진 사람만 들어가는 공간은 파티마다 사본을 띄운다. 사실상 지속 세계 안에 박힌 세션형 서버다. 붐비는 마을을 여러 사본(채널·레이어)으로 나누는 것도 같은 생각이다.
반대 방향의 유명한 예가 EVE Online이다. 전 세계 플레이어가 샤드 하나(Tranquility)를 함께 쓰고, 성계(solar system) 하나를 서버 노드 하나에 올린다. 성계 하나에 수천 척이 몰리면 그 노드의 CPU가 모자라는데, 성계를 더 쪼갤 수는 없다. CCP는 그 노드의 게임 시간만 느리게 흐르게 해 명령 대기열이 넘치지 않게 하는 길을 골랐다. 2011년 시간 늦추기(TiDi) 개발 블로그가 이를 소개했고, 정상 속도의 10%까지 늦춘다. 서버를 늘릴 수 없을 때 공정함을 지키려고 시간을 늦춘 셈이다.
세계를 공간 단위로 잘게 나눠 여러 서버(worker)에 나눠 맡기고 경계를 자동으로 옮기려 한 시도도 있었다. Improbable의 SpatialOS가 대표적이었는데, 이를 쓴 MMO 몇 편이 문을 닫거나 개발이 취소됐고 회사는 이후 다른 사업에 집중했다. 공간 분할은 개체가 한곳에 몰리면(큰 전투) 결국 한 서버가 그 지점을 맡아야 한다는 문제를 피하기 어렵고, 경계를 넘나드는 상호작용이 많을수록 서버 사이 통신이 늘어난다. 지금 대부분의 MMO는 샤드·구역·인스턴스를 섞는 쪽에 머문다.
틱 레이트와 대역폭: 얼마나 자주, 얼마나 많이
서버는 고정 간격으로 입력을 모아 세계를 한 걸음 진행하고(틱), 결과 스냅샷을 클라이언트에 보낸다. 틱 레이트가 64면 틱 하나에 쓸 수 있는 시간이 15.625ms, 20이면 50ms다. 이 예산 안에 물리, 판정, 직렬화를 다 끝내야 하고, 끝내지 못하면 틱이 밀린다. 틱이 화면 체감에 주는 영향은 멀티플레이 노트의 틱 레이트 절에 있으니, 여기서는 서버 쪽 비용인 대역폭을 계산해 본다.
100명 배틀로얄에서 서버가 초당 30번 스냅샷을 보내고, 개체 하나가 델타 압축(지난번과 달라진 값만 보냄) 뒤 평균 24바이트라고 하자. 모든 플레이어를 모두에게 보내면 스냅샷 하나가 2396바이트라 UDP 패킷 하나에 안전하게 담기는 1200바이트를 넘어 둘로 나뉘고, 서버 한 대가 경기 하나에 58.8Mbps를 내보낸다. 각 플레이어 근처에서 보이는 30개체만 보내는 관심 영역(area of interest, AOI) 필터를 걸면 18.4Mbps로 준다. 멀리 있는 개체는 더 드물게 보내는 식으로 송신 횟수를 절반으로 줄이면 9.2Mbps다.
- 대역폭은 서버 밀도를 정한다: 기기 한 대에 경기를 몇 개 올리느냐는 CPU만이 아니라 네트워크 카드와 클라우드의 송신 요금(egress)에도 걸린다. 송신이 줄면 같은 기기에 경기를 더 올리고, 요금도 준다.
- 패킷 크기를 넘기지 않는다: 1200바이트를 넘는 UDP 데이터그램은 경로 어딘가에서 조각나거나 버려질 수 있다. 한 스냅샷이 패킷 여럿으로 나뉘면 하나만 잃어도 전체를 못 쓴다. 우선순위가 높은 개체부터 담고 넘치는 것은 다음 틱으로 미룬다.
- 올라오는 쪽은 작다: 클라이언트가 보내는 것은 입력뿐이라 수십 바이트다. 다만 잃어버린 입력을 대비해 최근 입력 몇 개를 함께 실어 보내는 경우가 많다.
지역과 지연: 서버를 어디에 둘까
빛은 광섬유에서 1ms에 약 200km를 간다. 서울에서 미국 서부까지 왕복만 100ms가 넘으니, 서버를 어디에 두느냐가 틱 레이트보다 체감 지연을 더 크게 바꾼다. 그래서 게임은 지역(region)마다 플릿을 따로 두고, 경기를 잡을 때 플레이어가 어느 지역으로 갈지를 고른다. 기본 원칙과 핑의 정의는 네트워크 노트에 있다.
- 핑으로 고른다: 클라이언트가 지역마다 둔 측정용 주소(UDP 에코 등)에 핑을 보내 왕복 시간을 매치메이커에 함께 보낸다. 매치메이커는 경기에 든 사람 모두에게 괜찮은 지역을 고른다. GameLift Servers의 대기열 문서도 플레이어 지연 값을 받아 지역의 우선순위를 정하고, 첫 지역에 자리가 없으면 다음 지역을 찾는 방식을 설명한다.
- 지연과 대기를 바꾼다: 가장 가까운 지역만 고집하면 사람이 적은 시간에는 경기가 잡히지 않는다. 기다릴수록 핑 한도를 넓히는 식으로 둘을 맞바꾼다(게임 백엔드 노트의 매치메이킹 절).
- 엣지와 전용망: 큰 클라우드 지역만으로는 모자란 곳에 작은 데이터센터(엣지)나 베어메탈을 더 둔다. 인터넷 경로 자체를 고치려는 회사도 있다. 라이엇 게임즈는 실시간 앱을 위한 인터넷 고치기 1편에서 일반 인터넷 경로가 실시간 게임에 맞지 않는 이유를 들고, 자체 백본(Riot Direct)을 깐 경험을 연재했다.
- 지역마다 다른 서버, 하나의 계정: 경기 서버는 지역에 흩어지지만 계정·인벤토리 같은 백엔드는 한두 곳에 모이는 경우가 많다. 경기 서버가 백엔드를 부르는 일은 경기 시작과 끝에만 하도록 설계해, 대륙을 건너는 지연이 게임 도중에 끼지 않게 한다.
규모와 비용: 버퍼, 스팟, 그리고 업체
서버 기동에는 수십 초에서 몇 분이 걸린다. 매치메이커가 서버를 달라고 할 때 그제야 띄우면 플레이어가 그만큼 기다린다. 그래서 늘 Ready 서버를 몇 대 미리 띄워 두는 버퍼 방식으로 늘린다. Agones의 플릿 오토스케일러 문서의 Buffer 정책이 그대로 이 생각이다: Ready 서버를 몇 대 또는 몇 퍼센트 유지하고, 최소·최대 대수를 정한다.
- 버퍼 크기: 버퍼가 작으면 몰릴 때 대기가 늘고, 크면 노는 서버 값을 낸다. 버퍼가 감당할 것은 "서버 한 대를 새로 띄우는 동안 들어오는 경기 수"다. 피크 시간이나 대회·업데이트 날처럼 예측되는 몰림은 시간표로 미리 늘린다.
- 줄이기가 더 어렵다: 경기 중인 서버는 끌 수 없으니, 줄일 때는 Ready 서버만 지우고 경기 중인 기기가 비기를 기다린다. 경기를 기기 몇 대에 몰아 담아야(packing) 빈 기기가 빨리 생긴다.
- 스팟·선점형 인스턴스: 클라우드가 남는 용량을 싸게 빌려주고 필요하면 2분 예고 뒤 회수한다. 경기가 끊길 위험이 있어, GameLift Servers의 스팟 플릿 문서처럼 온디맨드와 섞어 대기열에 넣고, 회수가 잦은 인스턴스 유형을 피해 배치한다. 경기 시간이 짧을수록 스팟이 잘 맞는다.
- 클라우드와 베어메탈 섞기: 늘 쓰는 바닥 용량은 싼 베어메탈이나 예약 인스턴스로, 피크 위쪽만 클라우드로 받는 혼합(hybrid) 구성이 흔하다.
호스팅 업체도 바뀐다. Unity는 Multiplay 게임 서버 호스팅의 직접 지원을 2026년 3월 31일에 끝내고 소프트웨어를 Rocket Science Group에 넘겼으며, 서비스는 Rocket Science의 호스팅 페이지로 이어진다. 같은 해 Hathora는 AI 회사에 인수되며 게임 서버 사업에서 물러났고, GamesBeat 보도에 따르면 고객을 Nitrado로 넘겼다. 이 일로 멀티플레이를 잃게 된 게임도 있었다. 서버 쪽 코드를 업체 SDK에 얇게 감싸 두고(Ready·Shutdown·Health 정도), Agones 같은 표준 위에서도 돌 수 있게 해 두면 업체를 옮기는 비용이 준다.
장애와 복구: 체크포인트와 호스트 이전
서버는 죽는다. 세션형 경기는 죽으면 그 경기를 무효로 하고 보상을 돌려주는 정도로 끝내는 경우가 많지만, 지속 세계나 긴 협동 경기는 진행을 잃으면 안 된다. 그래서 서버가 상태를 주기적으로 밖에 저장하는 체크포인트를 둔다. 서버가 죽으면 관리 계층이 Unhealthy로 표시하고 새 서버를 띄워, 마지막 체크포인트를 읽어 이어 간다.
- 무엇을 언제 저장하나: 거래·아이템 획득처럼 잃으면 안 되는 일은 그 자리에서 바로 백엔드에 쓰고(트랜잭션), 위치·체력처럼 조금 되돌아가도 괜찮은 것은 몇십 초마다 한꺼번에 저장한다. 바로 쓰는 쪽은 복제 지연에 따라 아이템이 둘로 늘거나(dupe) 사라지는 버그가 생기기 쉬운 곳이라 정확성이 중요한 시스템 노트의 멱등성·원장 기법을 그대로 쓴다.
- 상태 저장소: 자주 바뀌는 세션 상태는 빠른 키-값 저장소(키-값 노트의 Redis 등)에, 영구 기록은 관계형 DB에 둔다.
- 호스트 이전(host migration): 리슨 서버나 P2P에서 호스트가 나가면, 남은 피어들이 미리 정한 규칙(연결이 가장 좋은 사람 등)으로 새 호스트를 뽑고 나머지가 그에게 다시 붙는다. 피어들이 평소에 경기 상태를 복제해 두어야 이어받을 수 있다. 전용 서버가 있으면 이 문제 자체가 없다.
- 다시 접속: 플레이어 쪽 장애(와이파이 끊김)가 더 흔하다. 서버가 경기 중 나간 사람의 자리를 잠시 비워 두고, 백엔드가 "지금 경기 중인 서버 주소"를 기억해 재접속을 받으면 된다.
프로토콜: UDP 위에 필요한 만큼만
TCP는 모든 바이트를 순서대로 전달한다. 게임에는 이것이 오히려 짐이다. 0.1초 전 위치를 담은 패킷 하나가 사라지면 TCP는 그것을 다시 받을 때까지 뒤에 온 최신 위치까지 붙잡아 둔다(head-of-line blocking). 최신 위치만 있으면 되는 게임은 그 패킷을 그냥 버리고 싶다. 그래서 실시간 게임은 대부분 UDP를 쓰고, 필요한 신뢰성만 그 위에 직접 얹는다.
- 게임 신뢰 계층: 패킷마다 순번을 달고, 받은 쪽은 최근 순번 32개쯤을 비트로 묶어 매 패킷에 함께 돌려보낸다(ACK 비트필드). 보낸 쪽은 어떤 패킷이 사라졌는지 알고, 그 안의 메시지 가운데 "반드시 가야 하는 것"(아이템 획득, 채팅)만 다시 보낸다. Glenn Fiedler의 UDP 위 신뢰성·순서·혼잡 회피 글이 이 구조를 단계별로 설명한다.
- QUIC: UDP 위에서 연결, 암호화(TLS 1.3), 독립된 여러 스트림을 제공하는 표준이다(RFC 9000). 스트림끼리는 서로를 기다리지 않아 TCP의 줄 막힘이 스트림 안으로 좁혀진다.
- 브라우저 게임: 브라우저는 UDP 소켓을 직접 열 수 없다. WebSocket은 TCP라 위의 문제가 그대로이고, MDN의 WebTransport API 문서가 설명하는 WebTransport는 HTTP/3(QUIC) 위에서 순서 없는 데이터그램과 스트림을 함께 준다. P2P라면 WebRTC 데이터 채널도 쓴다.
- 보안: 서버 주소는 공개되므로 디도스와 위조 패킷을 전제로 설계한다. 접속 토큰(매치메이커가 발급하고 서버가 검사)으로 아무나 붙지 못하게 하고, 큰 응답을 작은 요청에 돌려주지 않는다(증폭 공격). 자세한 방어는 보안 노트에 있다.
- Agones Documentation — Agones
- Agones Game Server Client SDKs — Agones
- Fleet Autoscaler Specification — Agones
- How hosting with Amazon GameLift Servers works — AWS
- Game session placement queues — Amazon GameLift Servers
- Reduce game hosting costs with Spot fleets — Amazon GameLift Servers
- Steam Datagram Relay — Steamworks Documentation
- Multiplayer Services SDK — Unity Documentation
- Introducing Time Dilation (TiDi) — CCP Games
- Fixing the Internet for Real-Time Applications: Part I — Riot Games
- Reliability, Ordering and Congestion Avoidance over UDP — Glenn Fiedler
- RFC 9000: QUIC — IETF
- WebTransport API — MDN
- Multiplay Game Server Hosting — Rocket Science Group
- Hathora acquired, will exit game infrastructure biz — GamesBeat