rein's world

NDC 참관기: 마비노기 영웅전 자이언트 서버의 비밀

오늘 들은 세번째 세션.

Managed runtime (정확히는 C#) 위에서 작성한 (아마도) 최초의 MO/MMORPG 서버인 마영전에 관한 일종의 포스트모템이었다. 기대했던 얘기를 다 들은건 아니지만 나름대로 ‘생각한 문제점’들에 대한 얘기를 많이 듣게 되었다.

요약이나 기타 내 촌평이 잘못된 부분에 대한 지적은 매우매우 환영한다.

요약

단일 서버: 기술적 도전과제로, 개발 초기에는 MORPG였음 (그로 인해 약간 구조가 빗나간 점도 있는 듯 하다)

목표:

  • Scale-out (horizontal-scaling)
  • 서비스라는 아주 작은 기능을 하는 단위로 역할 분담
  • Operation (트랜잭션?) 이라는 성공/실패와 이에 대한 콜백이 달린 객체를 사용함
  • 서비스를 찾게 해주는 네임 서버(위치 서비스?) 제공
  • 이 서비스들을 연결해주는 통합 네트워크

이를 통해서 서버를 추가할 때마다 성능이 올라가는 구조를 만들자

특히 개별 역할을 최대한 작게 쪼개서, ‘서비스’가 처리하도록 한다.부하가 많은 ‘서비스’는 여러 개의 인스턴스를 띄워서 밸런싱 한다.이 ‘서비스’들을 묶는 통합 네트워크와, 이 ‘서비스’들의 위치를 확인하는 네임 서버 기능이 있다.그리고 이 ‘서비스’는 단일 스레드 프로세스화 해서 작업을 쉽게 함. ((다만 이 ‘서비스’는 개별적으로 재시작할 수는 없는 구조라고 한다))

예를 들어, ItemService에 Item 제거 요청을 보낸다면,

var itemDisposeOperation = new ItemDisposeRequest(character-id, item-id);
 SendRequest('ItemService', itemDisposeOperation);

하는 식.

예상대로() 사내 테스트에서 성능이 안나와서, 추상화 수준을 변경. 개별 서비스 하나를 하나의 물리 서버가감당하지 못할 정도의 성능이 나와서, 하나의 서비스를 쪼갤 수 있는 (=샤딩과 비슷한 개념?)개념을 도입했다고 한다. 단순히 객체와 서비스만 있던 것에서, 이를 묶는 ‘엔티티’ 개념을 추가. 각 서비스는 담당하는엔티티만 처리하게 변경.‘엔티티’는 한 캐릭터의 ‘전체 인벤토리’, 한 캐릭터의 ‘우편함 데이터 전체’, 혹은 ‘하나의 전투’처럼 좀 큰 단위로하고, 이를 직접 사용해서 look-up 부하를 줄임.이 엔티티를 ‘획득’하는 것은 DB 서버에서 이루어진다. (결국 DB 서버가 병목이 되서 진정한 의미의 horizontal scaling은 안되긴 한듯) 2010년 초의 그랜드 오픈에서 30대의 물리 서버, DB 서버 2대와 ? 대의 웹 서버를 통해 동접 5만의 부하를 버티게 되었다고 한다.

문제점:

  • 프론트엔드 (메시지를 클라이언트에게 받아서 라우팅하는 서비스)의 비대화. 결국 이건 사실 상 god 클래스 아닌가 싶다.
  • Entity가 새는(leak) 문제: .Net 을 써서 생기는 객체 수명 관리 문제: ref-counting으로 순환 참조 문제 dangling-reference 문제 등등.

내가 질문했던 GC관련된 부분은 초기를 제외하고는 그다지 문제는 아니었다고 함. 초기에는 서버가 뻗을 수 있는 문제였다고 (이건 GC나 메모리 릭으로 스왑-아웃하게되면 모든 종류의 서버에서 일어날 수 있다)

촌평

이와 유사한 커널 구조가 떠오르는 사람도 있을 거다. 이건  OS 이론에서 말하는 마이크로 커널 비슷한 구조다. 코어한 커널에서는 데몬(=서비스)간 IPC(=통합 네트워크)와 스케쥴러, 메모리 맵핑 처리등만 제공하고, 실제 기능은 이 데몬 간의 (IPC를 쓰는) 통신으로 만드는 방식.분산 서버로 만들 때도 이 IPC를 네트워크 수준까지 확장하면 되기 때문에 한 때 매우매우 각광을 받았다.아마 마비노기 영웅전 팀도 비슷한 목적으로 이를 채택한 거 같다.

하지만 마이크로 커널의 최대 문제는 공유 메모리를 쓰는 모놀리딕 커널과는 달리 IPC를 통한 통신 부하가 꽤 크고(마영전 팀의 세션에서 ‘메시지 시리얼라이제이션 부하가 크다’란 말과 일맥 상통), 이로 인해서 라우팅 단(IPC 레이어)이 복잡해진다.

캐시 풋 프린트(cache foot-print)도 문제. 한 서비스에서 다른 서비스로 넘어갈 때, 거의 필연적으로 이미 한쪽 캐시에 올라간 데이터를 가져오게 되는데, 이로 인해 양쪽 캐시가 같아지고, 한쪽에 수정하면 다른 쪽도 (필요도 없는데) 캐시가 invalidate 된다. 쓸 데 없는 인터커넥션 대역폭 낭비가 Orz. 물론 ‘한 서비스’ 수준에서 처리가 끝나면 상관없지만, 설명한데로라면 ‘한 오퍼레이션’을 처리하기 위해 여러 서비스를 거쳐야 하기 때문에, 캐시를 여기저기 다 밟고다니는 문제가 생길 것 같다. 안 그래도, GC를 쓰는 이상 캐시 문제는 생길 수 밖에 없는 상황인거 까지 생각하면

일단 .Net 언어인 이상 GC에서 자유로울 수 없다. 객체 생명 주기를 맘대로 조절 못하는 것에다가 캐시에 ‘핫’하게 존재할 ‘수명이 다한 객체’를 실제로 재활용할 때까지 시간이 걸리거나 — Linus Torvalds가 성능이 나쁘다고 이런 ‘지연된 free’를 비판한 바 있다 — .net 언어를 쓰는 장점(=무한한 메모리의 추상화)를 포기하고 slab 할당자 처럼 객체를 캐싱하고 반환해주거나 해야한다.

이런 점들을 생각하면 ‘적어도 당분간’은 아예 더 생산성 높은 언어(=Python?)를 쓰거나 지금 처럼 C++을 쓸 듯

게다가 현재 마영전 라이브 서비스의 상황을 보면 ‘채널’이 있는 MMORPG랑 큰 차별성이 있는지는 약간 의문임. WoW처럼 던전 플레이는 같은 서버가 아니어도 가능하다면 지금 정도의 시스템과 ‘유저가 느낄 수 있는 큰 차이’는 없지 않을까. 통합 경매장 정도의 차이?

WoW나 Aion같은 게임도 지금이 ‘서버’를 ‘채널’로 바꾸고, 파티 시스템과 전장, 경매장을 합치면 (사실 일부는 이미 통합된 상황) 큰 차이는 없을 것 같다. 오히려 완성도 높은 마을이란 점에서 우위에 있을듯도.