Applying the Actor Pattern to MMO Servers
들어가며
지난 글(링크)에서는 POSA 2의 동시성 패턴들 — Reactor, Proactor, Actor — 을 간단하게 소개하고 살펴보았습니다. 이론적 배경을 이해했으니, 이제 실전으로 넘어갈 차례입니다.
근데 이제.. 실제 게임 만들 때 Actor 모델을 어떻게 적용한다는 건가?
이 질문에 정해진 답은 없습니다. 하지만 Actor의 적용 범위에 따라 실제 프로그래머가 코딩하는 환경이나 완성된 시스템의 동작 특성은 제법 큰 차이가 발생합니다. 이번 글에서는 MMO 게임속 상황을 예시로, 두 가지 대조적인 접근 방식을 살펴보겠습니다.
MMO 서버의 도전 과제
먼저 저는 지금은 MMO 게임 프로젝트를 진행하고 있지는 않아요. 그보다는 좀 더 인터랙션이 간소한 수집형 턴제 전투형 게임 서버를 개발 중입니다. 하지만 동시성 문제를 해결하는 예시와 설명을 위해서는 대량의 동시 접속자와 복잡한 상호작용이 요구되는 MMO 서버가 좀 더 실감나는 사례라고 생각합니다.
MMO 서버는 동시성 프로그래밍의 극한을 보여주는 영역입니다.
- 수천 명의 동시 접속 — 하나의 월드에 수천 명이 실시간으로 상호작용
- 복잡한 상태 공유 — 캐릭터, 몬스터, 아이템, 발사체가 서로 영향을 주고받음
- 낮은 지연 요구 — 100ms의 지연도 플레이어 경험을 해침
<출처: 아이온 요새전. 이미지는 본 글의 내용과 직접적인 관련이 없습니다.>
전통적인 lock 기반 접근은 이러한 규모에서 한계를 드러냅니다. 아주 간단하게, 캐릭터 a가 b를 한 대 때려볼까요?
1 | // 전통적인 lock 기반 접근 |
Lock의 순서를 관리하고, 데드락을 피하고, 경합을 최소화하는 것은 시스템이 커질수록 기하급수적으로 어려워집니다.
Actor 패턴은 이 문제에 대한 우아한 해법을 제시합니다. 그런데 한 가지 중요한 설계 결정이 남아 있습니다. Actor의 범위를 어디까지로 잡을 것인가?
예시 시나리오: 필드에서의 전투
구체적인 상황을 가정해 봅시다.
시나리오: 캐릭터 A가 캐릭터 B를 공격한다
이 단순해 보이는 상호작용에는 여러 처리가 얽혀 있습니다.
- A의 공격 판정 (스킬 쿨다운, 마나 소모)
- B의 피격 판정 (회피, 방어력 계산)
- B의 HP 감소
- 주변 캐릭터들에게 전투 이펙트 브로드캐스트
- B가 사망 시 아이템 드랍, 경험치 분배
- B의 사망으로 트리거되는 다양한 게임 이벤트들. 퀘스트 완료, 업적 달성, 땅이 갈라지며 보스몹 등장(갑자기?) 등등..
이 시나리오를 Actor 패턴으로 처리할 때, Actor의 범위를 어떻게 설정하느냐에 따라 이후 구현은 크게 달라집니다.
접근 1: Coarse-grained — Zone 단위 Actor
Zone이란?
캐릭터가 존재하는 가상의 공간(World)은 완전하게 무한할 수는 없기 때문에, 보통은 논리적 & 물리적 한계를 고려한 일정 단위로 분리하고 다시 이어붙여서 만들어집니다. 이 단위는 여러가지 이름으로 불립니다. 채널이라 부르기도 하고, Zone이라 부르기도 합니다. 이 글에서는 Zone이라고 지칭하겠습니다.
대부분의 게임 내 상호작용은 같은 Zone 안에서 이루어집니다. 캐릭터끼리 전투하고, 아이템을 줍고, 몬스터를 사냥하는 일은 모두 하나의 Zone 내에서 발생합니다. 반면 서로 다른 Zone 간에는 상호작용이 거의 없습니다. 기껏해야 Zone 이동(텔레포트, 포탈) 정도입니다.
이런 특성을 고려하면, Zone 전체를 하나의 Actor로 만드는 접근이 자연스럽습니다.
공격 처리 흐름
1 | // Zone Actor 내부 |
장점
1. 구현이 단순하다
Zone 내의 모든 객체가 같은 Actor에 속하므로, 객체 간 상호작용이 일반적인 메서드 호출과 같습니다. 메시지를 주고받을 필요 없이 직접 상태를 읽고 쓸 수 있습니다.
2. 일관성이 자동으로 보장된다
“A가 B를 공격하는 동안 B가 다른 Zone으로 이동”하는 상황이 구조적으로 발생하지 않습니다. 모든 처리가 순차적이므로 race condition이 없습니다.
3. 디버깅이 쉽다
단일 스레드에서 순차 실행되므로, 문제 발생 시 콜스택을 따라가면 원인을 찾을 수 있습니다.
단점
1. 멀티코어를 활용할 수 없다
Zone 하나는 CPU 코어 하나만 사용합니다. 8코어 서버에서 8개 Zone이 균등하게 분산되어 있다면 괜찮지만, 한 Zone에 플레이어가 몰리면 그 Zone은 단일 코어로 모든 부하를 감당해야 합니다.
2. 핫스팟 문제
보스 레이드, 공성전처럼 많은 플레이어가 한 Zone에 모이는 상황에서 성능이 급격히 저하됩니다. Zone당 처리량에 상한이 생깁니다.
1 | 8코어 서버, 3개 Zone 운영 중 |
3. 확장성 한계
서버를 증설해도 Zone 단위로만 분산되므로, Zone 내부의 부하를 분산할 방법이 없습니다. 결국 단일 Zone에 많은 유저가 몰리지 않도록 게임 디자인을 조정하거나, Zone에 입장 가능한 최대 유저 수를 제한해야 합니다.
접근 2: Fine-grained — Object 단위 Actor
이번에는 트래픽 처리 능력을 증가시키기 위해, 더 자잘한 단위로 쪼개보겠습니다. 게임 내의 각각의 오브젝트(캐릭터, 몬스터, 아이템 등)를 독립된 Actor로 만드는 접근입니다.
공격 처리 흐름
1 | // Character A Actor |
장점
1. 멀티코어를 최대한 활용한다
각 Actor가 독립적으로 실행되므로, 시스템은 가용한 모든 코어에 작업을 분산할 수 있습니다. Zone에 1000명이 몰려도 1000개의 Actor가 병렬로 처리됩니다.
1 | [Zone: 1000명] |
2. 수평 확장이 가능하다
Actor는 위치 투명성을 가지므로, 이론적으로 여러 서버에 걸쳐 분산할 수 있습니다. 부하가 증가하면 서버를 추가하여 대응할 수 있습니다.
3. 장애 격리
한 Actor에서 예외가 발생해도 다른 Actor에 직접 영향을 주지 않습니다. 시스템의 일부 장애가 전체로 전파되지 않습니다.
단점
1. 구현 복잡도가 상승한다
모든 상호작용이 비동기 호출로 이루어지므로, 단순한 “A가 B를 공격”도 여러 단계의 콜백이 됩니다. 순차적 사고에 익숙한 개발자에게는 진입 장벽이 있습니다.
2. 일관성 보장이 어렵다
“A가 B를 공격하는 요청”과 “B가 Zone을 떠나는 요청”이 동시에 발생하면? 요청 순서와 상태 정합성을 신중하게 설계해야 합니다.
3. 메시지 폭증 가능성
모든 상호작용이 비동기 호출이므로, 설계를 잘못하면 호출이 기하급수적으로 증가할 수 있습니다. 이 문제는 별도의 최적화 전략이 필요하며, 추후 다른 글에서 다뤄보겠습니다.
비교 정리
| 구분 | Coarse-grained (Zone) | Fine-grained (Object) |
|---|---|---|
| Actor 단위 | Zone 전체 | 개별 오브젝트 |
| 멀티코어 활용 | ❌ Zone당 1코어 | ✅ 최대 활용 |
| 구현 난이도 | ✅ 낮음 | ⚠️ 높음 |
| 일관성 보장 | ✅ 자동 | ⚠️ 명시적 설계 필요 |
| 확장성 | ❌ Zone 단위 한계 | ✅ 수평 확장 가능 |
| 핫스팟 대응 | ❌ 취약 | ✅ 강함 |
어떤 방식을 선택할 것인가
앞서 살펴본 바와 같이 두 접근 방식은 명확한 트레이드오프가 있습니다. 게임의 규모와 방향성에 맞는 구조를 선택해야 합니다.
하지만 장르 이름 자체가 MMO(Massively Multiplayer Online) 인 만큼, 대량의 트래픽을 처리하는 것은 MMO 엔진의 핵심 요소입니다. Fine-grained 방식의 구현 복잡도 문제는 분명 존재하지만, 적절한 추상화와 도구를 통해 상당 부분 완화할 수 있습니다.
이와 관련하여 NDC25에서 발표된 “MMO 서버에서 태스크 그래프를 활용한 확장성 있는 멀티스레드 아키텍처” (넥슨게임즈)는 매우 좋은 참고 사례입니다. Fine-grained Actor 환경에서 복잡도를 관리하는 소중한 실전 노하우를 소개하고 있습니다. (링크 : https://www.youtube.com/watch?v=6nYtK7kNiH8) 이 발표의 내용을 차근히 풀어서 설명하는 포스팅을 작성해도 지금의 시리즈 연작과 잘 어울리겠다는 생각이 듭니다. 일단 제가 하고 싶은 말을 다하고 여력이 된다면… 한 번 시도해 보겠습니다.
마치며
이번 글에서는 지난 글에서 패턴의 이름으로만 소개했던 Actor 모델을, 실제 MMO 서버 구현에 어떻게 적용할 수 있는지 살펴보았습니다. Zone 단위의 Coarse-grained 접근과 Object 단위의 Fine-grained 접근을 비교하며, 각각의 장단점을 확인했습니다.
다음 글에서는 (진짜로) C#으로 작성해본 경량 Actor 구현기를 살펴봅니다. 게임서버에서 특히나 중요하게 여기는 빠른 응답성, 객체 수명주기 관리, 대량 트래픽을 위한 가벼운 구현 등을 어떻게 달성했는지 구체적인 코드와 함께 다루어 보겠습니다.
다음 글: C#으로 구현하는 경량 Actor (예정)