이번 주 팀 세미나: NDC 2011 마영전 서버 세션 정리

이전에 여기에 올려 놓은 내용이지만, 팀 세미나 시간에 간략히 정리해서 짧게 얘기했던 슬라이드 정리.

이전 글을 다 보신 분은 굳이 읽지 않아도 됩니다. 밑의 링크는 HTML5 지원하는 웹 브라우져에서 봐야 함.

NDC2011 – 마비노기 영웅전 서버 관련 세션 정리

Beamer — LaTeX .sty 중 하나 — 로 처음 작성한 슬라이드. 예전에 타카하시 메서드로 발표한 적이 있긴 하지만, ‘준비’만 따지고 보면 이게 이제까지 써본 툴 중 제일 쉬웠던 듯.
MacOSX용 키노트가 쓰기 편한 툴이긴 하지만, 내 영혼은 아무래도 *nix 터미널에 묶여있는 듯 하다.

결과물 자체가 PDF로 나오기에 아예 다른 출력포맷 신경 안쓰고 작업할 수 있는 것도 좋음. 결과물도 그다지 크지 않고…
다만 한 줄씩 혹은 한 문단씩 나오게 하는 효과를 만들면 이게 k 개 페이지에 쪼개져서(…) 페이지가 생성된다는 점은 좀 별로다만;

하지만 회사 스타일 문서 써서 만들려면 이걸 못 쓴다는 건 좀 심각한 단점. 아니다 내가 beamer 테마 파일 만드는 법을 익히면 되나?-_-;; 이건 정말 야크 쉐이빙일거 같은데?

NDC 참관기: Server Burner: 범용 네트워크 스트레스 테스트 툴

NDC 2011 2일 차(5/30)에 있었던 하재승 군의 세션.

여기에는 하재승 군과 같은 팀에 있어서(…) 좀 더 우호적인 관점의 내용이 있다는 점을 생각하며 읽어주기 바람;

이하는 내용 요약

게임 오픈 혹은 대규모 패치 후에 서버가 폭주(?)하는 사례가 많다.

네트워크 스트레스 테스트 툴이 필요하다?

  • 예상되는 스트레스 상황 테스트 / 반복 실행
  • 미리 문제를 찾고 해결하자

Pros.

  • 쉽게 테스트 작성
  • 대량의 테스트 진행
  • 테스트 진행 중 데이터 자동 수집 / 모니터링

미리 작성된 테스트 시연

간단한 채팅 서버를 가지고,

  • 1 명 들어와서 채널에 메시지 말하고 나가기
  • 100 명 들어와서 같은 동작
  • 테스트 동안 모니터링한 결과 보여주기

잘 알려진(…) 올해의 게임 Minecraft를 가지고,

  • 여러 마리의 봇이 로그인해서,
  • 봇들은 (미리 지정한) 유저를 따라다니고, 유저가 삽질(마우스 왼쪽 클릭)하는걸 따라함(…)
  • 100 명 수준이 되어가면 서버가 심하게 랙이 걸리는 걸 볼 수 있음[1]
  • 더불어, 이 동안의 시스템 모니터링한 그래프를 보여주기

테스트 자동으로 작성하기

  • 클라이언트 -> 복수의 서버 군(로그인, 채널, 게임, 챗, 몬스터, …)의 통신을 캡쳐해서 자동으로 테스트를 작성
  • 상대적으로 쉽고 간편하게 + 버그 재현에 써먹기
  • 바로 캡쳐해서 쓸 수는 없다 -> 툴을 이용해서 쉽게
  • 미리 만든 스트레스 테스트 용 아이디 (특정 prefix + 숫자)가 필요함

툴을 이용해서 테스트 스크립트 수정

한 눈에 패킷 보기, 패킷에 들어가는 값을 특정 전역 변수랑 연동/편집, 스크립트 내보내기… 를 처리하는 GUI 편집툴 제공

Client.Send_LoginRequest(A, "test"..index(), "testpass")
local pkt = Recv(A)
if pkt.__id__ ~= Server.LoginResult then fail() end

…같은 형식의 스크립트가 최종적으로 나옴.

결과 분석

  • 가상 유저 별로 받은 패킷 순서, 전달된 패킷의 개별 값, 특정 메시지 간 시간, 특정 행동(로그인)에 걸린 시간 값 등등을 측정
  • 서버 자원 모니터링

요약

프로토콜 파일 작성

  • 캡쳐/스트레스 테스트에 이용
  • 클라이언트 — 서버 간 패킷 내용 정의 ;(Python, lua, C#, C++ 코드 생성)
  • 패킷 정의가 없으면 해당 프로젝트에 쓰는 방식을 분석하고 정의 파일 만들기
  • 구조체 기반인 경우 아예 C++ 헤더를 파싱해서 만들기도 함
  • 헤더/푸터,암호화,체크섬 등 처리 가능

UDP 통신 / HTTP 통신 / 네트워킹 모듈 기능 테스트

계획

  • 복잡한 테스트 수행 (장시간 반복하기; 현재는 쉘 스크립트로 대체)
  • 테스트 결과 분석 강화
  • 네트워크 상태 시뮬레이션 (패킷 로스, 지연 상황)

촌평.

온라인 게임으로 한정할 경우, 많은 네트워크 테스트 도구가 갖는 문제가 뭐냐하면,

  • 부적당한 추상화 수준 : TCP/IP 패킷 레벨, HTTP 메시지, …
  • 부하 테스트 시나리오를 생성할 방법
  • 부하 테스트 시나리오를 유지 관리할 방법
  • 충분한 수준의 부하를 생성할 방법
  • 복수의 서버군이 있을 때 이에 대한 추상화는 어떻게 할지

등등?

이런 문제에 대한 접근법으로, 적당한 도메인 분리 + 개별 공략은 타당한 선택지라고 판단된다. 대략 서버 버너 구조 자체가,

  • 프로토콜 데이터의 별도 표현(yaml)
  • 메시지 캡쳐 / 편집 프로그램
  • (수동) 추가적인 스크립트 편집
  • 복수의 머신에서 동시에 부하 스크립트 동시 시작/종료/통계 처리

로 쪼개져 있다. 그리고 이 중 수동으로 스크립트를 추가 수정하는 부분만 빼면 많은 경우 그다지 복잡할 게 없다.

우선 추상화 수준의 문제.

사실 게임…이라고 해도 장르 자체가 다르면 기술적인 의미에서 추상화 수준이 서로 다르다. 그래서 ‘메시지’라는 좀 저수준이지만 공통(?)일 수 있는 수준을 잡고, 여기에 대해 UDP/TCP/혹은 응용프로그램 수준 메시지로 잘 쪼개서 처리하는 접근은 적당하다고 생각한다. 대략 게임이라면 TCP, TCP 기반의 메시지 (HTTP라거나? 전용 포맷?), UDP (전용 p2p 프로토콜 류) 정도를 생각할 수 있는데, 이거에 대해서 프로토콜 데이터만 기술해주면 대략 이 수준의 추상화는 메서드 호출처럼 표현할 수 있다.[2] 메시지를 특정 형태로 기술하는 일은, 팀 내에서야 이게 공통 포맷이라(…) 시간이 0이 걸리고, 사내 다른 팀 껄로도 거의 전환 시간 없이 가능했던걸 생각하면 어찌어찌 가능은 할 듯 하다. 대략 엔지니어 하나 붙여놓고 하루 안에 적용이 가능했으니…[3]

예를 들어 인증 메시지 보내는 것은: SendAuthMessage(utf-8-string-id, bytes-credential, bytes-session-key) 수준 정도로?

다음으로 메시지 캡쳐, 편집.

일단, 부하테스트 시나리오 생성을

  • 패킷을 아예 캡쳐하고, 이걸 적당히 조작(시연 동영상을 보면 좀더 이해가..) 하거나,
  • 아예 중간에 이걸 릴레이하는 서버 (개발팀이라면 언제라도 가능한) 를 두고 모든 메시지를 가로채거나
  • 서버가 여러 대인 경우에도 이걸 적당한 스트림으로 보여주고, 일부를 병합하거나

하는 식으로 실제 메시지를 가지고 만들어내고, 이걸 lua 스크립트 수준에서 처리하면 된다는 점이…

복수 머신에서 부하 자체를 생성하는 일

머신 한 대에서 생성하는 부하에는 한계가 있다. 왠만큼 시간을 들여 짜고 + 튜닝하지 않고서야, 머신 당 유저 수는 크게 제한된다. 차라리 가짜 유저 간 상호 작용을 줄이고 머신 붙인 만큼 유저를 더 만들어 내는게 대략 맞는 방향인 거 같다.
부하자체는 테스트 스크립트를 시작하는 프로세스와, 이 테스트 스크립트를 받아 실제로 메시지를 보내는 k개의 프로세스를 써서 보내기 때문에 (물론 머신도 k개에 준하게…) 물리 머신 수만 충분하다면 원하는 수준의 부하를 만들 수 있다. 다만 밑단의 lua 스크립트는 짜야겠지만(…).

Q/A 팀에서 일일이 부하테스트를 짜는건 약간 힘들지 몰라도, 서버 프로세스를 짜는 프로그래머라면 큰 무리 없이 쓸 수 있는 수준의 도구라고 생각한다.

  1. 테스트할 때 쓴 서버는 대략 Win7 x64, RAM 4GiB, Q8600 정도 []
  2. 다만 UDP는 별도로 .dll을 짜서 python/lua 바인딩을 제공해줘야 한다… []
  3. 강연 때도 나온 얘기지만 아예 xml 기반의 프로토콜이나 압축, 별도 체크섬 같은거 붙이는건 큰 일이 아님… []

NDC 참관기: 네트워크 비동기 통신, 합의점의 길목에서

넥슨 마비노기 2팀[1] 김재석 씨의 강연이었다.

네트워크 통신 일반론이 아니라 이전에 참관기를 쓴 마영전 서버 구조를 생각하면서 내용 요약을 읽어주기 바란다.
네트워크 프레임웍 개발을 목표로 데브캣 스튜디오의 게임을 개발하면서 얻은 경험에대한 설명이었다.
강연 중에도 부연 설명한 부분이지만, 이 이하에서 주로 얘기하는 두 개의 주체는 다음 의미다

  • 서비스: 비동기 네트워크 메시지를 처리하는 주체
  • 클라이언트: 비동기 네트워크 메시지를 보내는 주체

이하는 강연 요약. 잘못된 사항, 빠진 부분 등에 대한 지적/수정요청은 매우 매우 환영(…).

목표

  • 네트워크 코딩 스트레스를 주지 말자
  • 지나친 하위 레이어의 지식을 요구하지 말자

문제가 되는 부분은,

  • 네트워크 통신은 비지니스 로직이 아니라는 것
  • 정보가 필요한 시점에 ‘즉시 계산되는 것’으로 생각할 수 있으면 좋겠다
  • C++과 C#을 모두 지원해야하는 점[2]

방향

  • 네트워크 코드 생성
  • C/C++ 상호 운용
  • 논리 흐름에 따라 코드를 생성한다 (흐름을 끊지 말자)
  • 저수준의 네트워크 기술 지식은 숨기자

시스템 프로그래머와 응용 프로그램 프로그래머의 ‘설계 목표’가 상충된다.

그리고 이런 문제의 본질은 ‘적당히 돌아가면 그냥 쓰고 미완이 되는’ 데에 있다…

1차 시도 메시지 생성 / 프로토콜 반복기(enumerator)

메시지 생성

  • C#: 2.0 Lightweight code generation 사용 (4.0이라면 ExpressionTree)
  • C++: VS 매크로 혹은 하드 코딩으로[3]

pros.

  • C#에서 데이터 포맷만 정의하면 끝남

cons.

  • C++ 코드 생산성이 떨어진다
  • C# 형식의 가변성 혹은 generic처리의 문제

프로토콜 반복기

(Client)
var op = new Operation()
op.onComplete += () => { … }
op.onFail += () => { … }
RequestOperation(op)
(Service)
IEnumerable<Object> Run() {
yield return info.ID;
yield return info.Name
}

pros.

  • 클라이언트 코드를 짤 때 연관 코드가 한 자리에 모인다

cons.

  • cons. enumerator 를 배우는데 드는 비용
  • C++ 에서 사용할 수 없다[4]
  • 중간 처리가 필요한 비동기 작업이 적기 때문에, 굳이 enumerator를 쓸 이유가 없다

2차 시도 delegate 기반으로 코드 생성

마영전에 사용한 방법이라고 한다.

C# delegate를 IDL로 사용했다.  Client용 코드는 C++로, Service용 코드는 CIL을 생성하게 했다.

Hindley-Milner type inference를 사용한다. 불변성은 ref 가변성에 대해 공변성은 out, return 반공변성은 in 이용;

pros.

  • C# IDL만 가지고 클래스가 자동 생성된다

cons.

  • 프로토콜의 연관성을 기술할 수 없고,
  • 클라이언트/서버가 대칭성이 없고
  • 배경 지식이 많이 요구되며  유지 보수가 좀 불편하다 (C++/CIL)

 

3차 시도: interface 기반 코드 생성

차기 프로젝트에 사용하고 있는 방법이라 한다.

C# ‘interface’를 IDL로 사용해서 추상 클래스를 양쪽(C++/C#)에 생성한다.

  • Client/Service CodeDOM을 생성
  • C++/CIL을 대칭적으로 생성한다
  • Microsoft Asynchronous Programming Design Patterns의 권고안 대로 네이밍 및 기타 등등
  • Trivial한 serialize 형식은 C++에 복제해서 기타 작업을 간편하게

pros:

  • 대부분의 코드를 자동 생성하고
  • C++/Common Language Spec. 양쪽에서 일관성있게 작업

cons:

  • 상속을 써서 생기는 제약


촌평.

강연자체는 시도했던 방법들을 분석했다는 점만으로도 매우 훌륭하고, 전반적으로 말이 좀 빠른게 불만이긴했지만, 각 방법들을 간결하게 분석한 점이 괜찮았다.

다만 내가 기대한게 ‘일반적인 상황’에서의 비동기 통신 방법에 대해 얘기할거라고 생각한거여서 약간 망했음. 일단 여기서 언급한 건 (대부분의 경우) 마영전 서버에서 언급한 것과 같은 구조를 배경에 깔고 있다. 내가 만드는 서버 런타임에서 목표로하는 추상화 수준과 너무 달라서 Orz. 사실 이 세션 들을 때 기대한 것은 현재 참여하고 있는 한 프로젝트에서 클라이언트-서버 인터페이스 (네트워크 레이어와 그 래퍼 수준) 를 좀 달리 해볼까 하는 고민에서였는데,일단 서버 구조가 목표로하는 추상화 수준과, 이 추상화에서 내보이려는 부분이 마비노기 영웅전 서버와는 상당히 상이하기 때문에 GG. 내가 만드는 서버 런타임은 전반적인 비동기 메시지 ‘처리’에 집중하고 있고, 이쪽은 ‘통신 추상화’에 집중하고 있어서;;제목에서 기대한 거랑, 내가 가서 들은게 약간 덜 겹쳤다는 것?

잘 이해가 안가는 점: ‘왜 C#으로 중간 단계를 기술하는 것(IDL로 사용하는 것)에 집착할까?

나는 꽤나 다른 방법으로 근 몇 년간 시도해서 잘 쓰고 있다. 데이터를 설명하는 종류의 언어[5] 를 IDL로 써서 주고 받는 코드를 몽땅 생성하는 것.
물론 기반 런타임은 필요한 언어마다 짜긴 해야하지만, 이건 안 바뀌는 부분이라..

대략 데이터 포맷 파일인 yaml 파일이 적당한 생성기를 거치고나면, C++, C#, lua, python, 매우 실험적으로 Google go … 소스코드로 변환되고 이 과정은 IDE나 스크립트 수준에서 빌드할 때 자동으로 생성;
이 중 어느 언어 쌍을 골라도 서로 통신할 수 있게 만들었다.사실 이 생성기가 위에서 언급한 불변성/공변성을 몇 가지 경우에 대해 만족하지 못하긴 하지만, ((메시지 보낼 때 vector<int>를 vector<long long>대신 못 넣는건 확실히 불편하다))  실용적인 목적에서 큰 차이는 없을 것 같다.
일단 이 대칭적인 코드 생성기가 lua/python에서 안되면 팀에서 쓰는 부하 테스트 도구(ServerBurner)를 못 쓰기 때문에 약간 불편한 부분이있긴하지만, 일단 ‘돌아가고’ ‘쉽게 (다른 사람이) 확장할 수 있는’게 목적이니 나는 큰 불만이 없다(…완전히 신입으로 들어온 사람도 잘 적응하는거 보니 그렇게 느껴짐).

또한, C#의 enumeration 개념을 이해하는게 프로그래머들에게 벽이면, 왜 C++에서 우회할 방법 — 아마 맨 처음 기반 코드는 C++이었을텐데 — 이 많고 (boost function, c++ 0x lambda) 러닝커브도 큰 차이 없을텐데 왜 그건 시도하지 않았을까 하는 점.
2차 시도의 비동기 메시지 처리 형태는 boost::function과 (이후에) C++ 0x lambda로 쉽게 만들 수 있었고 (그리고 쉽게 쓰고 있고), 3차 시도의 구현 자체는 IDL이 data 기술 언어란 점만 빼면 시연 과정과 비교해봐도 큰 차이가 없는데…

그냥 이건 취향 문젠가?

  1. 안내 책자가 없어서 틀릴 수 있다; 잘못된 부분 수정 좀;; []
  2. 이건 데브캣 프로젝트 들의 언어 선택 문제인 듯 합니다 []
  3. 나중에는 주로 하드 코딩하게 되었다고 한다 []
  4. 세션 중에 연사가 말한 내용이기도 하고, 내가 예전부터 했던 일이기도 한데, 이 부분은 C++ 라이브러리나 C++0x의 기능을 쓰면 충분히 쓸 수 있다 []
  5. lua (6개월), xml(2년),yaml(2년) 순으로 바뀌었고, 최종적으론 계속해서 yaml을 쓸 듯 하다. []

NDC 참관기: 변화량 추적을 중심으로 한 저비용 고효율의 지속적인 코드퀄리티 관리법

6/1일자 오후 세션이었던 네오플의 송창규 님의 강연입니다.

이하는 간략한 요약.

게임 업계의 패러다임 변화: 새 게임 만들기 => `성공한 게임’의 개선, 유지

그런 이유로 지속 가능성이 중요하다

코드 퀄리티 관리

깨진 창문 효과: 관리되지 않는다는 느낌을 주면 안된다.

하지만, 라이브 서비스의 경우, 그 규모에 압도 되서 (코드 베이스가) 슬럼화될 수 있다.
청소 메타포: 매일 매일 청소할 게 생기고, 하지 않는다고 못 버티는건 아니지만, 계속 해서 불편해짐..

코드 퀄리티 관리와 비슷하다. 그러나 프로젝트가 시간이 흘러 코드 베이스는 계속 해서 (무한정) 커지지만, 거기에 투입할 수 있는 프로그래머 수에는 한계가 있다는 점은 큰 차이

난관

  • (코드 베이스가 커서) 깨진 창문을 찾지도 못한다
  • 이에 들어가는 비용
  • 개선해도 잘 안보이고, 개선 사항이 유지되기도 힘들다
  • (라이브 서비스) 코드는 계속해서 오염된다

=> 학습된 무기력을 낳는다

어떻게 할까?

자동화 + 시간에 따른 변화에 중점

(정적 분석에서는) 코드를 데이터로 보고 이를 분석한다. 여기에 시간 개념을 포함해서 소스 코드를 시간에 따라 `변화하는 데이터’로 보자.

어떻게 나눌까?

논리적 분류, 의존성에 따른 분류, 그리고 변동성에 따른 분류.  프로젝트 유지 비용을 낮게 하려면 변동성이 높은 것과 낮은 것을 잘 나눠야 한다.

단, 변동성의 개념에 따라, 낮은 변동성인 요소가 높은 변동성인 요소에 의존하면 높은 변동성인 요소가 되어버린다.

 

변동성의 측정

‘정량적으로 측정?’ => ‘소스 저장소의 변화량을 측정’
기대 효과: 많이 바뀐 곳이 (아마) 중요한 곳일 것이다

변동성이 높은 곳(파일)을 개선하면?

  • 신규 프로그래머의 진입 장벽이 낮아지고, 정보 과잉을 해소할 수 있다
  • 개선 효율이 높다
  • 변동성이 낮은 파일들을 묶어서 별도 프로젝트/dll로 분리할 수 있다[1]

문제:  만약 파급력이 높고, 변동성도 높은 파일이 있다면? 명백히 좋지 않으니 어떻게 하지 않으면(…)

이런 파일을 어떻게 찾을까? 변동성, 파급력, 시간에 따른 파급력이 모두 높은 파일?

Python 스크립트로 소스 저장소 데이터를 측정해서,

  • 변동성이 높으면서 파급력도 높은 파일 : 시간에 따른 파급력 조사, 재컴파일된 파일 수 조사
  • 파일이 아니라 함수 수준에서 찾아보자
  • 로그 메시지의 특정 단어 Bug, fix, … 등에서 힌트를 추가
  • 함수 라인 별 변화량 추적 (LCS 쓰기; FAIL)

(이 부분에서는 던파, 크아 BnB, 메이플 스토리(일부는 누락) 소스 저장소 데이터를 모아서 보여줬다. 개략적으로 자주 변하는 파일과, 그 부분에 대한 간략간략한 개선 방향을 언급함)

숫자의 함정에 유의하자:

  • 이걸 기준으로 프로그래머의 능력을 평가하면 곤란 (커밋 당 버그 수 등등을 측정하면 망함; 인센티브 룰을 최대한 치팅하게 됨)
  • 릴리즈 전 빠른 커밋 과 빠른 버그 발견 및 수정은 오히려 권장되어야 함

 

지속 가능한 코드 퀄리티 관리 방법들

  • 영역을 구분하고 점진적으로 하자.
  • 직관의 함정: 추가되는 코드도 더럽혀 진다.
  • 다시 어지럽히지 말아야 한다

잘 안된다 -> 공정 개선이 필요

 

공정 개선 팁

Assertion: 특히 MT환경에서 효과가 좋았다.

  • Lock ordering 검사
  • needs-lock 매크로
  • Caller-thread check

등등.

Assertion을 나누는 것,

  • Domain에 따라 나누고 (perf-critical/non-critical)
  • 컴파일 시간에 할지를 나누고
  • 허용되는 액션에 따라 나누고 (ignore, msg-box, log, send-packet)
  • MsgBox 에 따라 나누고

 

Validator는 지속 가능성이 낮다: 강제하기 힘듬

지속가능하게 하려면?

  • 빌드 시스템을 이용하거나
  • commit hook을 이용해서 강제하거나
  • (새로 작성하거나 변경할 때는) 이를 검사하지만, 이전 코드 등에서는 이를 무시하게 할 수 있어야

 

매우 재밌게 들었다.

연사가 충분한 케이스 스터디 — 넥슨 내 장기간 지속된 프로젝트에서 추출한 데이터 — 로 `쓸 만 하겠다’라는 생각을 충분히 줬다. 이 아이디어를 당장 팀 내 저장소에 넣고 돌려봐도 쓸모 있을 듯 함.

이런 류의 세션에서는 역시 “실제로 해보고”, “케이스 스터디도 있고” 란 점이 주는 신뢰도는 정말 중요함…

Validator 같은 부분도 @ipkn 이 말하던 `스크립트 변수 edit-distance로 잘못 쓴거 찾자’랑 비슷한 느낌이기도 하더라.

첫 날 메이플 스토리 개발 스토리 세션에서도 말한 내용이지만, “기존 레거시 코드”에 대해서 예외를 인정하고, (특히나 변경되지 않는 부분이라면 더더욱) 새로 추가하거나 변경하는 부분에 중점을 두는 것은 “실제로 적용하기 위해선 꼭 필요한 정책”이라고 생각한다.

단순히 “새로 할 때 잘 하자”가 아니라, 라이브 서비스나 이미 충분히 진행 중인 서비스에서 적용할 수 있게, “저비용 고효율 방법론”을 제시한 점이 아주 좋았음.

  1. 빌드 시간 개선;  봐야할 장소 줄이기 등등; 연사가 측정한 바로는 변동성 적은 것만 묶어서 unity build하면 효과가 좋다고 한다(던파 클라 빌드시간 3x분 -> 1x분 []

NDC 참관기: 게임 애셋 스트리밍 패치

이승재 (http://ricanet.com) 군의 발표. 여기서 들은 내용을 간략히 정리해 보았다.

프론티어 팀에서 데스크탑 히어로즈(이하 DH)를 개발하면서 얻은 경험을 공유했다. 여기서 애셋은 그래픽 리소스, 사운드 등등의 게임 내 리소스를 말한다.

왜 스트리밍 패치를 했는가?

설치 용량 10 MiB 이하인 게임을 원해서 애셋 스트리밍 패치를 구현.
그 결과,

  • 작동을 위한 `최소한의 파일’ 만 받게 함 (다만 이 리소스를 사람이 수동으로 고르지는 않게)
  • 게임 패쳐를 내장해야 한다

어떻게 했는가?

1. DH는 스테이지 단위로 쪼개져 있다. 스테이지에서 필요한 걸 로딩 시점에 다운로드 받게

개별 애셋은 이름 / 크기 / CRC / svn revison 로 구분하도록 했다.

전체 애셋은 수 십 MiB -> 이들의 목록은 수 십 KiB -> 이에 대한 버전은 수 십 bytes 버전 파일을 비교하고, 필요하면 목록을 얻어와서 필요한 파일만 받도록 구현.

2. 지금 (스테이지) 필요한 파일만 `미리’ 로딩 화면에서 받는다 (플레이 중 멈추는 건 대략 좋지 않음)

  • 항상 필요한 파일 (bin, script, ui …, seed.lua?)
  • 현재 스테이지에서 필요로 하는 파일
  • 이들이 의존하는 파일

로 구분해서 이 파일들을 다운 받는다.

이를 위해 `애셋 의존성 분석’ 코드를 리소스 종류별로 만들었다.  이 의존성은 게임 요소에  따라 다르고, 리소스 종류가 추가되면 추가해야 함.
3. 다운로더

  • 메인 스레드가 `큐에 요청을 넣음’
  • 다운로드 스레드 (1~10) 가 이 큐에서 요청을 꺼내고, 해당 리소스와, 해당 리소스가 의존하는 리소스를 다운 받게 구현

기타 이슈

– 개발용 버젼은 스트리밍 패치를 (당연히) 끄게 했다. 근데 이렇게 했더니 개발 중에는 잘되는데 릴리즈에서 죽는 경우가 생김 (리소스 종류가 추가되거나, 꼭 필요한 파일인데 seed.lua에 추가 안하거나) + 개발 버젼에서도 처음 보는 종류의 리소스 같은 걸 검사하게 수정

– 가상 파일 시스템 : 제일 복잡한 서브 시스템이 되어버림

  • 임의 파일 추가 / 삭제 처리
  • MT read/write를 보장해야
  • 견고 해야 한다. 오류에 충분히 저항해야 함. 임의 시점에 클라가 죽을 수도 있다

그래서,

  • 파일 메타 데이터처리를 저널링으로 구현
  • 힙처럼 관리해서 지울 때 참조하는 애가 없어야 실제로 free-block으로 마크
  • 오버랩드 I/O로 처리 등등

– 수 많은 예외

파일이 없어서 죽거나 기타 등등 수많은 예외가 생겼고, 이에 대한 크래시 리포트 분석 및 예외 처리 작업 (현재 진행 형)

– 아바타

누가 내 `방’에 들어올지 모르니 항상 필요한 리소스로 처리.. 다만 이걸 더미 리소스로 보여주고, 백그라운드 로딩해서 바꿔치기 하는 등으로 할 수 있음

– 보너스

의존성 검사가 있기 때문에, `필요 없어진’ 리소스를 쉽게 파악하게 되었음

 

여기서 부터 내 촌평.

스트리밍 패치는 어느 정도 게임 의존적이긴 하겠지만, 많은 경우 쓸만한 곳을 찾을 수 있겠더라. (일단 내가 작업 중인 쪽만 해도…)

리소스 의존성을 `잘 기술하면’ 재사용될 부분도 많고, 전체 개발 프로세스에 + 요인이 될 듯함. (특히 비대해지기 쉬운 파일 묶음을 어떻게든 줄일 수 있으면…)

실질적으로 서버 비용이 줄어드는 것도 있고, VFS 같은 걸 만드는 건 꽤나 재밌기 때문에 (어이).

전체적으로, 발표 전반에서 `어째서, 어떻게, 왜’가 잘 드러나 있는 점이 만족스러웠다 (메이플 스토리 개발 스토리랑 비슷한 느낌; 예전 같은 팀이라?). 다만 해당 강연장이 강단을 빼고 어둡게 처리되어있는데, 카메라 플래시가 계속 터져서 집중하기 어려운 건 좀 그랬다; 이건 내년에 좀 해결되었으면 한다.

 

 

NDC 참관기: 메이플스토리 개발스토리 – 더 잘되는 게임을 위한 라이브 코어 개발

훌륭한 엔지니어인 황의권 군의 발표. 오늘 들은 세 개 세션 중 가장 재밌게 들었다.

 

내용 요약:

라이브 서비스이기 때문에, 제일 중요한 것은 `방어’. 그렇지만 `공격적인 업데이트’를 통해서(빅뱅 업데이트) 재탄성 했다. 이를 위해 지속적인 개선과정과 그에 따른 이슈 처리가 필요했다.

1. 창모드와 해상도 확장

DX8이 화면 주파수 동기화가 안되는 문제. 그리고 일부 고성능 머신의 GPU의 경우, 과부하가 걸리는 문제가 생겼음. 그래서 DX 9으로 이전하고, 잘 점검하고 실험하는걸로 해결.

해상도 확장의 경우 게임 화면, View-port, 툴, 데이터를 변경하는 문제가 있어서 생각보다 일이 컸다고 한다. 의외로 뷰포트를 `유저에게 친화적인’ 화면 아래쪽, 상태 표시 줄 쪽에 중심을 두는 방식으로해서 작업량을 적당히 유지할 수 있어다 한다.

2. 동접 갱신과 이에 따른 서버 부하 관리

우선 장비를 업그레이드. 하지만 32bits OS의 한계로 메모리 제한(2GiB)에 부딪힘.
/3GB 플래그로 유저 스페이스를 늘렸더니 kernel I/O 퍼포먼스가 떨어지고, 이로 인해 MS SqlServer 성능문제가 심해서 이 방법은 포기 했다. 그리고 WoWx64의 경우 테스트에 필요한 시간이 모자라서 채택하지 않음(제일 중요한건 `방어’). 어떤 의미론 정석대로 코드를 분석해서 메모리 사용량을 줄이는 걸로 대응하고, 메모리 사용량에 따라 `동시 접속자 수 제한(대기)’을 구현해서 해결했다.

3. 개발 환경 개선과 신규 시스템 추가

다음과 같은 변화를 줌.

  • VC6에서 VS 2008로 이전
  • 락 순서에 대한 Assertion추가[1]
  • CC.net 을 사용해서 지속적인 빌드를 만듬
  • TBB의 concurrent_hash_map 도입
  • lua 스크립트를 도입

레거시 코드 문제 때문에,

  • VS 6 호환성 옵션 활용
  • (일부 상황에 대해) lock-order assertion을 끄고, 예외를 인정하기로
  • 라이브러리 리뷰/수정 및 일부 사항에 대해선 예외를 인정하고 타협

4. 의의 및 정리

* 검증이 중요 (라이브 서비스!)
* (비슷하게) 실수가 용납되지 않는다

검토가 중요하고, 현재까지 수정한 매몰 비용을 생각할게 아니라, 수정에 따른 총 비용을 생각해야 한다. 특히 남들이 말리는 리팩터링이나 패치 전 자잘한 수정은 금기사항.

정리: 개인의 만족과 조직의 발전 사이의 어떤 지점. 라이브 서비스를 만드는 구성원들에게 `더 나아지고 있다’는 느낌을 준다는게 중요하다.

 

촌평

라이브 서비스의 절대 목표 `서비스를 유지’하는 것을 전제로, 어떤 문제를 겪고, 이걸 해결하는 방법에 대한 이유를 잘 설명해 준 것 같다.

내용 요약에 추가할 점, 잘못된 점 등을 지적해주면 매우매우 감사하겠다.

  1. 이건 데드락을 피하기 위한 매우 표준적인 방법이다 []

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

NDC에서 마영전팀이 발표한 마영전의 자이언트 서버 구조의 목표, 문제점.
이 구조는 마이크로 커널의 그것과 닮아서 그의 문제를 거의 그대로 가지고 있고, 이를 구현하는데 쓴 .Net 런타임의 문제 또한 가지고 있다는게 내 생각.

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

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. 다만 이 ‘서비스’는 개별적으로 재시작할 수는 없는 구조라고 한다 []