- 현재 및 개선 단계별 CCU 추정 (현재 30~50명 → Phase 전체 적용 시 5,000명) - M4 Mac Mini 기준 하드웨어 적합성 분석 - CCU별 네트워크 대역폭 계산 (AOI 적용 후 기준) - 환경별 (가정용/IDC/클라우드) 현실적 CCU 결론 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
17 KiB
MMORPG 아키텍처 적합성 분석 보고서
1. 분석 목적
현재 서버 구조가 다수의 플레이어가 동시 접속하여 실시간으로 상호작용하는 MMORPG에 적합한지 평가하고, 부족한 부분과 개선 방향을 제시한다.
2. MMORPG 서버의 핵심 요구사항
| 요구사항 | 설명 |
|---|---|
| 대규모 동시접속 (CCU) | 수백~수천 명이 같은 월드에서 실시간 플레이 |
| 저지연 패킷 처리 | 위치/액션 동기화는 50ms 이내 응답 필요 |
| 관심 영역 관리 (AOI) | 내 주변 플레이어만 패킷을 주고받아야 대역폭 절약 |
| 게임 루프 (Tick) | 고정 주기(예: 20~60Hz)로 월드 상태를 업데이트 |
| 존/채널 분리 | 지역별 부하 분산 및 인스턴스 관리 |
| 엔티티 관리 | 플레이어, NPC, 몬스터 등의 상태를 중앙에서 관리 |
| 서버 간 통신 | 로그인/로비/게임/채팅 등 역할별 서버 분리 |
| 장애 복구 | 서버 크래시 시 세션 복구, 데이터 무결성 보장 |
3. 현재 구조 평가
3.1 네트워크 계층
현재 구조:
단일 ServerBase → 단일 NetManager → 단일 스레드 PollEvents()
| 항목 | 현재 상태 | MMORPG 요구 | 평가 |
|---|---|---|---|
| 프로토콜 | UDP (LiteNetLib) | UDP 기반 필수 | 적합 |
| 직렬화 | ProtoBuf | 고성능 바이너리 직렬화 | 적합 |
| 폴링 모델 | 단일 스레드 while + Task.Delay(1) |
멀티스레드 or 고성능 이벤트 루프 | 부적합 |
| 패킷 라우팅 | type 기반 if 분기 | 핸들러 매핑 테이블 + 디스패처 | 부적합 |
문제점:
-
단일 스레드 병목:
PollEvents()가 단일 스레드에서 모든 패킷을 처리한다. CCU 500 이상에서 패킷 큐가 쌓이면 처리 지연이 발생한다. MMORPG에서 플레이어 100명이 초당 10패킷을 보내면 초당 1,000패킷인데,HandlePacket내부에서 DB 조회 같은 블로킹 작업이 끼면 전체 루프가 멈춘다. -
패킷 디스패처 없음:
HandlePacket(peer, hashKey, type, payload)하나로 모든 게임 패킷을 처리해야 한다. 패킷 종류가 17개인 현재도 거대한 switch문이 필요하며, 패킷이 늘어날수록 유지보수가 어려워진다. -
Task.Delay(1)해상도: Windows에서 실제로 ~15ms 간격으로 폴링된다. 60Hz 게임 루프가 필요한 MMORPG에서는 16.6ms마다 정확한 틱이 필요한데, 현재 구조로는 불가능하다.
3.2 세션 관리
현재 구조:
Dictionary<long, NetPeer> sessions (hashKey → peer)
Dictionary<int, NetPeer> pendingPeers (peerId → peer)
| 항목 | 현재 상태 | MMORPG 요구 | 평가 |
|---|---|---|---|
| 자료구조 | 일반 Dictionary | ConcurrentDictionary 또는 락 기반 | 위험 |
| 인증 | 8byte hashKey raw 전송 | JWT/OAuth + 토큰 갱신 | 부적합 |
| 세션 정보 | hashKey + NetPeer만 | 플레이어 ID, 존, 위치, 상태 등 | 부족 |
| 재연결 | hashKey 기반 피어 교체 | 세션 상태 전체 복구 | 부분 구현 |
문제점:
-
인증 보안 취약: 8바이트 hashKey를 평문으로 보내면, 패킷 스니핑으로 다른 사람의 세션을 탈취할 수 있다. MMORPG는 JWT 같은 시간 제한 토큰 + 서버 측 검증이 필수다.
-
세션에 게임 상태 없음:
Session클래스에HashKey와Peer만 있다. MMORPG에서는 세션이 플레이어의 현재 존, 위치, 파티, 인벤토리 캐시 등을 들고 있어야 패킷 처리 시 매번 DB를 조회하지 않는다. -
재연결 시 상태 미복구: WiFi→LTE 전환 시 피어만 교체하고 게임 상태(존, 위치, 버프 등)를 클라이언트에 재전송하는 로직이 없다.
3.3 게임 루프 & 월드 관리
현재 구조:
없음. GameServer 클래스는 빈 껍데기.
| 항목 | 현재 상태 | MMORPG 요구 | 평가 |
|---|---|---|---|
| 게임 루프 | 없음 | 고정 틱 (20~60Hz) | 미구현 |
| 존/맵 관리 | 없음 | Zone/Channel/Instance 분리 | 미구현 |
| AOI (관심 영역) | 없음 | Grid/Quadtree 기반 영역 관리 | 미구현 |
| 엔티티 시스템 | 없음 | Player/NPC/Monster 상태 관리 | 미구현 |
| 물리/충돌 | 없음 | 서버 권위 이동 검증 | 미구현 |
문제점:
이것이 가장 큰 갭이다. MMORPG 서버의 핵심인 게임 루프가 존재하지 않는다.
-
게임 루프 없음: 현재는 "클라이언트가 패킷을 보내면 서버가 반응"하는 순수 리액티브 구조다. MMORPG는 클라이언트 입력이 없어도 서버가 자체적으로 NPC AI, 몬스터 이동, 버프 타이머, 리스폰 등을 주기적으로 처리해야 한다.
-
AOI 없음: 현재
Broadcast()는 모든 접속자에게 패킷을 보낸다. 플레이어 100명이 모두 움직이면 초당100 × 100 = 10,000패킷이 발생한다. 1,000명이면1,000,000패킷이다. AOI 없이는 CCU 50만 넘어도 대역폭이 폭발한다. -
존/채널 없음: 모든 플레이어가 하나의
sessionsDictionary에 평면적으로 존재한다. "마을에 있는 플레이어"와 "던전에 있는 플레이어"를 구분할 방법이 없다.
3.4 데이터베이스 계층
현재 구조:
Handler → Service → Repository(Dapper) → MySQL
매 요청마다 새 커넥션 (풀링은 MySQL 드라이버에 위임)
| 항목 | 현재 상태 | MMORPG 요구 | 평가 |
|---|---|---|---|
| ORM | Dapper (경량) | 경량 ORM 적합 | 적합 |
| 커넥션 풀링 | MySQL 드라이버 위임 | 명시적 풀 관리 | 보통 |
| 캐싱 | 없음 | Redis/인메모리 캐시 필수 | 부적합 |
| 비동기 | async/await | 논블로킹 필수 | 적합 |
문제점:
-
캐시 계층 없음: 플레이어가 스킬을 쓸 때마다 DB에서 스탯을 읽으면 게임이 안 된다. MMORPG는 로그인 시 플레이어 데이터를 메모리에 올리고, 주기적으로 DB에 저장(Write-Back)하는 구조가 필수다.
-
게임 루프에서 DB 호출 위험: 현재는 패킷 핸들러에서 직접 DB를 호출하는 구조인데, 게임 루프 스레드에서 DB await를 하면 틱 지연이 발생한다. 게임 로직과 DB I/O는 반드시 분리해야 한다.
3.5 서버 구조 (스케일링)
현재 구조:
단일 프로세스, 단일 서버
| 항목 | 현재 상태 | MMORPG 요구 | 평가 |
|---|---|---|---|
| 서버 분리 | 모놀리식 단일 서버 | 로그인/로비/게임/채팅 분리 | 부적합 |
| 수평 확장 | 불가 | 존별 서버 분산 | 부적합 |
| 메시지 큐 | 없음 | Redis Pub/Sub, RabbitMQ 등 | 미구현 |
| 로드밸런싱 | 없음 | 게이트웨이 + 존 서버 라우팅 | 미구현 |
문제점:
-
단일 서버 한계: 하나의
ServerBase인스턴스가 인증, 에코, 게임 패킷을 모두 처리한다. CCU가 늘어나면 서버를 추가할 방법이 없다. -
서버 간 통신 없음: "A 서버의 플레이어가 B 서버의 플레이어에게 귓속말"이 불가능하다. 파티, 길드, 거래 등 크로스 존 기능을 구현할 수 없다.
4. 종합 평가
적합성 점수
| 영역 | 점수 (10점) | 비고 |
|---|---|---|
| 네트워크 프로토콜 | 8 | UDP + ProtoBuf 조합 우수 |
| 패킷 처리 파이프라인 | 3 | 단일 스레드, 디스패처 없음 |
| 세션 / 인증 | 3 | 보안 취약, 게임 상태 없음 |
| 게임 루프 | 0 | 미구현 |
| 월드 / 존 관리 | 0 | 미구현 |
| AOI (관심 영역) | 0 | 미구현 |
| 엔티티 시스템 | 0 | 미구현 |
| DB / 캐시 | 4 | Dapper 좋으나 캐시 없음 |
| 서버 확장성 | 1 | 단일 프로세스 모놀리식 |
| 종합 | 2.1 / 10 |
결론
현재 구조는 에코 서버 / 소규모 매칭 게임 (10~20명) 수준에 적합하며, MMORPG로 사용하기에는 핵심 시스템 대부분이 미구현 상태이다.
네트워크 기반(LiteNetLib + ProtoBuf)과 DB 레이어(Dapper + Repository 패턴)는 좋은 출발점이지만, 그 위에 올라가야 할 게임 서버 계층이 빠져 있다.
5. MMORPG 전환을 위한 로드맵
Phase 1: 게임 루프 & 엔티티 시스템 (최우선)
현재: 클라이언트 패킷 → ServerBase → GameServer(빈 껍데기)
목표: 클라이언트 패킷 → 입력 큐 → GameLoop(고정 틱) → 월드 상태 업데이트 → 브로드캐스트
구현 항목:
-
고정 틱 게임 루프
Stopwatch기반 정밀 타이머로 50ms(20Hz) 또는 33ms(30Hz) 간격 틱- 패킷 수신은 입력 큐에 적재, 게임 루프에서 꺼내서 처리
- 네트워크 I/O와 게임 로직 스레드 분리
-
엔티티 관리자
GamePlayer,GameNpc,GameMonster등 인게임 엔티티 클래스- 위치, 상태, 스탯을 서버 메모리에서 관리
- 로그인 시 DB → 메모리 로드, 주기적으로 메모리 → DB 저장
-
패킷 디스패처
Dictionary<ushort, Action<NetPeer, long, byte[]>>핸들러 매핑 테이블- 패킷 타입별 핸들러 자동 등록 (어트리뷰트 또는 초기화 시 등록)
예시 구조:
GameLoop
├── ProcessInputQueue() // 클라이언트 패킷 처리
├── UpdateNpcAI() // NPC 행동 업데이트
├── UpdateBuffTimers() // 버프/디버프 타이머
├── UpdateRespawns() // 몬스터 리스폰
├── BroadcastWorldState() // 변경된 상태만 클라이언트에 전송
└── SaveDirtyEntities() // 변경된 엔티티 DB 저장 (N틱마다)
Phase 2: 존/채널 & AOI
구현 항목:
-
존 시스템
Zone클래스: 해당 존의 플레이어/NPC/몬스터 목록 관리- 존 진입/퇴장 시 엔티티 이동
- 인스턴스 던전은 동적 Zone 생성/파괴
-
AOI (Area of Interest)
- 그리드 기반 관심 영역: 맵을 NxN 셀로 나누고, 플레이어가 속한 셀 + 인접 셀만 패킷 전송
Broadcast()→BroadcastToNearby(position, radius, data)로 교체- CCU 1,000명 기준 패킷 수: 1,000,000 → ~50,000 (95% 감소)
예시:
┌───┬───┬───┐
│ │ B │ │ B는 A의 AOI 범위 안 → 패킷 수신
├───┼───┼───┤
│ │ A │ │ A가 이동 패킷 발신
├───┼───┼───┤
│ │ │ C │ C는 AOI 범위 밖 → 패킷 안 받음
└───┴───┴───┘
Phase 3: 인증 강화 & 세션 확장
구현 항목:
-
JWT 기반 인증
- 로그인 서버에서 JWT 발급 → 게임 서버에서 검증
- hashKey 대신 토큰 기반 인증, 만료/갱신 처리
-
세션 확장
public class GameSession { public long AccountId { get; init; } public NetPeer Peer { get; set; } public GamePlayer? Player { get; set; } // 인게임 엔티티 참조 public Zone? CurrentZone { get; set; } // 현재 존 public DateTime LastActivity { get; set; } // AFK 감지 } -
재연결 상태 복구: 재연결 시 현재 존, 위치, 파티 상태를 클라이언트에 재전송
Phase 4: 캐시 & DB 최적화
구현 항목:
-
인메모리 캐시 도입
- 접속 중인 플레이어 데이터는 메모리에 상주
- Write-Back 패턴: 변경 사항을 큐에 쌓고 N초마다 일괄 DB 저장
- 아이템/스킬 테이블 등 정적 데이터는 서버 시작 시 로드
-
DB I/O 분리
- 게임 루프 스레드에서 직접 DB 호출 금지
- 별도 DB Worker 스레드에서 저장 큐 처리
- 긴급 저장(거래, 결제)만 즉시 처리
Phase 5: 서버 분산 아키텍처
구현 항목:
-
서버 역할 분리
클라이언트 ↓ 게이트웨이 서버 (인증 + 라우팅) ├── 로비 서버 (매칭, 파티, 채팅) ├── 게임 서버 A (마을 존) ├── 게임 서버 B (필드 존) ├── 게임 서버 C (던전 인스턴스) └── DB/캐시 서버 (Redis + MySQL) -
서버 간 메시지 큐
- Redis Pub/Sub 또는 RabbitMQ로 서버 간 통신
- 크로스 존 기능: 귓속말, 길드, 파티 초대, 거래
-
수평 확장
- 존 단위로 게임 서버 인스턴스 추가
- 게이트웨이가 플레이어를 적절한 존 서버로 라우팅
6. 현재 코드에서 재사용 가능한 부분
현재 코드가 완전히 쓸모없는 것은 아니다. 아래 부분은 그대로 또는 약간의 수정으로 재사용 가능하다.
| 컴포넌트 | 재사용성 | 비고 |
|---|---|---|
| LiteNetLib 기반 네트워크 | 높음 | UDP 전송 계층으로 그대로 사용 |
| ProtoBuf 패킷 정의 | 높음 | PacketBody.cs의 17개 패킷 구조 재사용 |
| PacketSerializer | 높음 | 직렬화/역직렬화 로직 그대로 사용 |
| ARepository + Dapper | 높음 | DB 계층 그대로 사용 |
| ServerBase 인증 흐름 | 보통 | JWT로 교체하되 pending → auth 흐름은 유지 |
| Session/재연결 로직 | 보통 | 확장 후 재사용 |
| Docker/compose 설정 | 높음 | 배포 파이프라인 그대로 사용 |
7. 권장 우선순위 요약
[즉시] Phase 1: 게임 루프 + 엔티티 시스템 + 패킷 디스패처
→ 이것 없이는 어떤 게임 로직도 구현 불가
[단기] Phase 2: 존/채널 + AOI
→ CCU 50명만 넘어도 Broadcast 폭발
[중기] Phase 3: 인증 강화 + 세션 확장
→ 보안과 재연결 안정성
[중기] Phase 4: 캐시 + DB 분리
→ 성능 병목 해소
[장기] Phase 5: 서버 분산
→ CCU 1,000+ 대응
8. 현재 수용 가능 인원 추정
처리 성능 (단일 스레드 기준)
PollEvents() 틱 간격: ~15ms (Task.Delay(1) Windows 해상도)
초당 틱 수: ~66회
틱당 처리 가능 시간: 15ms
| 시나리오 | 패킷 1개 처리 시간 | 유저당 초당 패킷 | 예상 CCU |
|---|---|---|---|
| 에코 테스트 (현재) | ~0.01ms | 10 | ~1,000명 |
| 간단한 게임 로직 | ~0.1ms | 10 | ~500명 |
| 실제 MMORPG 로직 | ~1ms | 10 | ~50명 |
| DB 호출 포함 | ~5ms | 10 | ~10명 |
HandlePacket 안에 뭘 넣느냐가 전부다. 지금 빈 껍데기라서 수백 명이 되는 거지, 로직이 들어가는 순간 급락한다.
개선 시 수용 인원 (단일 서버)
| 개선 단계 | 핵심 변경 | 예상 CCU |
|---|---|---|
| 현재 | 단일 스레드, 동기 처리 | 30~50명 |
| Phase 1 | 게임 루프 + 입력 큐 분리 | 200~500명 |
| Phase 1 + 2 | + AOI 적용 | 1,000~2,000명 |
| Phase 1~4 | + 캐시 + DB 분리 | 3,000~5,000명 |
| Phase 5 | 멀티 서버 분산 | 서버당 5,000 × N대 |
개선별 효과 설명
Phase 1 (게임 루프) - 4~10배 향상
현재: 패킷 100개 → 하나씩 순차 처리 (100번 컨텍스트 스위칭)
개선: 패킷 100개 → 큐에 모아서 → 틱 1번에 일괄 처리 (배치)
Phase 2 (AOI) - 가장 극적인 효과
현재: 유저 1,000명 이동 → 브로드캐스트 1,000 × 1,000 = 1,000,000 패킷/틱
개선: 유저 1,000명 이동 → AOI 반경 내 ~20명에게만 = 20,000 패킷/틱 (98% 감소)
AOI가 가장 임팩트가 크다. 네트워크 부하가 O(N²) → O(N)으로 바뀐다.
9. 하드웨어 & 네트워크 요구사항
M4 Mac Mini 깡통 (16GB RAM) 기준
| 리소스 | M4 Mac Mini | 서버 필요량 (CCU 3,000) | 여유 |
|---|---|---|---|
| CPU | 10코어 (4P+6E) | 게임 루프 1~2코어 + 네트워크 1코어 | 충분 |
| RAM | 16GB | 유저당 ~10KB × 3,000 = 30MB + 앱 ~500MB | 매우 충분 |
| 디스크 | 256GB SSD | MySQL + 로그 | 충분 |
M4 싱글코어 성능이 서버용 Xeon보다 빠르다. 게임 루프는 본질적으로 싱글스레드 위주라서 M4로 CCU 3,000~5,000 처리 가능.
네트워크 대역폭 계산 (AOI 적용 후)
유저 1명이 받는 패킷:
- 주변 플레이어 이동: ~20명 × 30Hz × 50byte = 30KB/s
- NPC/몬스터 상태: ~10개 × 10Hz × 40byte = 4KB/s
- 기타 (채팅, 파티): = 1KB/s
──────────────────────────────────────────────────────
유저 1명 ≈ 35KB/s (다운) + 5KB/s (업) = 40KB/s
| CCU | 필요 대역폭 | 필요 회선 |
|---|---|---|
| 100명 | 4MB/s (32Mbps) | 100Mbps 충분 |
| 500명 | 20MB/s (160Mbps) | 1Gbps 필요 |
| 1,000명 | 40MB/s (320Mbps) | 1Gbps 필요 |
| 3,000명 | 120MB/s (960Mbps) | 1Gbps 빡빡 |
| 5,000명 | 200MB/s (1.6Gbps) | 2.5Gbps 이상 |
환경별 결론
| 환경 | 병목 | 현실적 CCU |
|---|---|---|
| M4 Mac Mini + 가정용 기가 인터넷 | 업로드 속도, NAT 테이블 | 100~500명 |
| M4 Mac Mini + IDC/클라우드 1Gbps 보장 | 대역폭 | 2,000~3,000명 |
| 클라우드 서버 + 2.5Gbps 이상 | CPU | 3,000~5,000명 |
병목은 Mac Mini 성능이 아니라 네트워크다. CPU/RAM은 남아돌지만, 가정용 인터넷은 업로드 제한(보통 다운의 1/10)과 공유기 NAT 테이블 한계가 있다.