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

오늘 들은 세번째 세션. 나름대로 만족스럽게 들은 듯… 다만 밥을 못 먹고갔더니 너무 배고팠다 ㅠㅠ

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

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

요약

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

목표:

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

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

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

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

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

하는 식.

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

문제점:

  • 프론트엔드 (메시지를 클라이언트에게 받아서 라우팅하는 서비스)의 비대화. 결국 이건 사실 상 가드 클래스 아닌가…
  • 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같은 게임도 지금이 `서버’를 `채널’로 바꾸고, 파티 시스템과 전장, 경매장을 합치면 (사실 일부는 이미 통합된 상황) 큰 차이는 없을 것 같다. 오히려 완성도 높은 마을이란 점에서 우위에 있을듯도.

  1. 다만 이 ‘서비스’는 개별적으로 재시작할 수는 없는 구조라고 한다 []

Published by

rein

나는 ...

16 thoughts on “NDC 참관기: 마비노기 영웅전 자이언트 서버의 비밀”

  1. 와… 깔끔한 정리 글 잘 봤습니다. 그런데, 혹시 서버 간의 분산 처리 시 지연시간 단축을 위해 InfiniBand, RDMA 등을 사용했다던지… 그런 h/w 적인 이야기는 없었나요? 요즘 레이턴시 줄이는데 이런저런 고민을 하고 있는지라… ㅎㅎ

    1. 발표 내용대로면 그냥 일반적인 TCP/IP 연결을 쓴 거 같았습니다.
      근데 InfiniBand 썼다는 회사가 있긴 한가요? (EVE online 빼고…)

      수요일인가 목요일인가 그 팀에서 발표 하나 더 하던더 거기서 뭔가 더 나올지도 모르겠습니다;;;

  2. 저도 재미있게 봤습니다. 처음 구조보고 어떻게 성능이 나온거지? 했는데 역시나 안 나왔다는 이야기가 orz 그래도 서버 설계자의 로망과도 같은 구조에 도전해서 정식 서비스까지 해냈다, 이런 부분은 인상적이었습니다. 작업이 나름 즐거웠을 듯…

    1. 작업은 꽤 재밌을 거 같습니다. 일단 일상적인(?)구조가 아니니…

      근데 서버 대수 vs. 동접수만 놓고보면 좀 `…’ 스런 반응 밖에 안나오더라고요;;

  3. 저도 꽤 흥미롭게 본 세션이었지만 저도 역시나 통합경매장 통합던전 같은 구조로 생각하고 있습니다.
    채널을 잘 안보이게 하고 통합경매장 통합던전 구조라면 유저가 느끼기엔 큰 차이는 없을거 같네요

    1. 저도 그런 점에서 좀 의아했던 세션입니다. 애초에 잡았던 목표와, 현재 라이브 서비스랑 거리감이 느껴지거든요;;

  4. 정리와 촌평 감사 드립니다. 기존 MMO서버에서 서버 경계를 넘는 경제시스템과 파티시스템이 추가된 것과 큰 차이가 없는 상태인 건 맞는 것 같아요. 유저가 어느 서버에서 할 지 고민할 필요가 없다는 점은 여전히 장점입니다만 ^^; WOW에서 다른 서버에 있는 유저와 파티를 연결해주는 파티찾기를 만들었을 때 깜짝 놀랬었죠.
    C#을 사용해서 가장 큰 장점은 예외처리만 잘 해주면 서버가 안 죽는다는 거 같아요. C++로 만들 때와 비교하면 널 포인터 억세스에도 서버가 안 죽는 게 개인적으로는 가장 행복했습니다. 신입 프로그래머들이 사소한 실수로 큰 장애를 일으키는 일도 적었고요. 사소해 보이지만 중요한 장점이죠.
    캐시 풋프린트 문제는 두 서비스에 한 데이터의 캐시가 올라오는 일을 없앴기 때문에 일어나진 않았는데, 그 대신에 세션 중에 이야기했던 “옵저버가 많으면 엔티티가 변경되었을 때 큰 부하가 생긴다” 라는 문제가 생긴 것 같습니다.

    1. 1. `새로운 구조 만들기’가 목표가 아니라면 WoW처럼 `상대적으로 쉽고 현실적인 목표’를 추구하는게 실제 서비스에 어울린다는게 제 생각입니다. 유저가 원하는게 `서버 선택이 필요없다’라면 현재의 WoW는 그 목표를 `거의’ 만족하고 있다고 생각합니다.

      캐시 풋 프린트 문제는 단순한 데이터 공유 문제를 말하고자 하는게 아닙니다. CPU 캐시에 계속 쓸 것도 아닌 데이터가 올라오는 걸 매니지드 런타임에서는 막을 방법이 (사실 상) 없는데, 현재처럼 데이터를 한 서비스에서 다른 서비스로 토스하는 형태라면 두 서비스가 사용하고 있는 (각기 다른) 코어의 CPU 캐시를 모두 점유하게 됩니다. 그런 점에서 다른 모노리딕한 구조들(네트워크 메시지를 받아서 로직까지 풀 스윙하는 구조라거나)보다 CPU 캐시 사용 면에서 페널티를 받는다는 얘가를 하고 싶었던 겁니다.

  5. 1. 현업에 없으니 이런 정보가 좋네요. 그런데 하나 궁금한데 업계의 GC 성능 패널티에 대한 평균적인 인식이 어떠한가요? 사실 MSFT도 내부적으로 C#을 정말로 많이 쓰고 C# 기반 서버도 많아서 자신 하는 것 같은데, 우리나라 서버 프로그래머들 사이에서는 어떠한 인식인지 궁금하네요.

    2. GC가 초반에만 문제가 있었고 그 뒤에는 그닥 문제가 아니라고 들으셨는데, 그게 어떤 근거로 말할 수 있는 것일까요? 특별히 프로파일링으로 그런 결론을 낼 수 있나요? 궁금하네요.

    3. 말씀하신 캐시 풋프린트 문제는 잘 이해가 안 가는데, 만약 두 서비스가 last-level 공유 캐시가 있는 프로세서 내의 코어들에 있다면 문제가 안 된다고 생각합니다. 물론, 중복 데이터가 L1, L2에는 올라 갑니다만 이 정도 오염은 보통 감수합니다. 또, 읽기 전용으로 데이터가 대부분 쓰일 것 같은데 (그러면 코히런시 문제는 없고), 수정을 한다 하더라도 last-level 공유 캐시가 있는 상태라면, L1/L2 만 없애주면 되고, 최근에 와서(샌디브릿지) $to$ 이동이 빨라져서 큰 패널티는 없을 것 같습니다.

    문제가 되려면 문제가 되려면, 공유 캐시가 전혀 없는 Pentium D 같은 놈이나, 두 서비스가 다른 소켓이 다른 코어에서 작동하고 있어야 합니다. (소켓 넘어서는 L3 캐시 끼리도 코히런시 해야하니깐) 위의 서비스가 모두 하나의 프로세스로 구현이 된다면 다른 소켓에 각각 다른 서비스가 돌아갈 수 있는데, 스레드로 구현이 되어있다면 최신 운영체제는 최대한 한 소켓에 할당해줍니다. Windows 7은 다중 소켓에서 확실히 그렇게 스케줄링 하더라고요.

    1. 1. 이건 저희가 라이브까지 가서 써본게 없어서 잘 모르겠네요. 회사 내 다른 팀 벤치마킹이나 테스트로는 대략 GC에서 멎어버리는 경우가 있다고 합니다. (C#이 아니라 Java였지만…)

      2. 저 초반이란게 프로세스 시간이 아니라, 개발 후 라이브가기까지의 시간 축에서 초기, 후기란 얘기였습니다. 정확히 어떻게 고쳤는지는 말 안해줬지만(…), 나중에는 메모리 사용량과 패턴을 바꿔서 GC 피해(?)를 최소화 했다고…

      3. 저 전체 시스템이 서버 여러 대에 나뉘어진 프로세스고 어느 소켓에 있을지도 예측할 수 없는데다가, 심지어 다른 머신일 수 있어서 저렇게 적어봤습니다. 안그래도 GC때문에 핫하지 않은 데이터가 CPU 캐시에 있을 가능성이 좀 있는데, 거기다가 사용 간격이 벌어진다거나 하면(…)

  6. 말씀하신대로, 서비스의 분산이 이루어지는 순간부터 캐시 수준의 이야기는 물건너가는거죠. 너무나 당연하고 분산을 통한 이익(존재한다면)으로 레버리지 해야겠죠. 웹 서비스의 백엔드에서는 이러한 구조는 좀 더 보편적인데, 게임 같이 응답 속도가 매우 중요하고 서비스간 통신이 많을 것 같은 도메인에서도 이런 구조를 성공적(?)으로 가져갔다는 것이 신기하네요.

    그나저나 서비스라는 기능적인 분화와 물리적인 분화는 일치시킬 필요가 없는데, 분산 자체를 목표로 하다보면 모든 것을 분산할 수 있게 하자..라고 해서 서비스와 물리적인 단위가 일치하게 되어버리는 경우들이 종종 있어요. 가급적이면 하나의 객체를 다루는 서비스들이 인접해서 동작할 수 있도록 하고, 여러 객체가 함께 다루어져야 하는 경우 등의 서비스만 별도의 물리적인 단위로 구성하는 것이 좋은데 말이죠. (글을 다시 읽어보니 이게 엔터티군요;)

    게임 서버의 구조에 대해서는 잘 모르지만, 재미있는 글이라 코멘트 남겨봅니다.

    1. 해당 세션에 나온 내용만 놓고 보면, 게임 서버가 share-(almost)-nothing 은 아니라서 (물론 그런 자연적인 경계가 생기는 부분도 있지만) 너무 나눠서 본 손해가 좀 큰 듯 했습니다.

      이게 정말 성공적일지는 앞으로 북미/중국 서비스가 좀 더 진행되면 (여러가지 경로로) 알 수 있을 듯합니다. 건승을 기대해봅죠

Leave a Reply