LMAX 거래소가 게임서버 아키텍쳐에 주는 계시 #2

부제: <거래엔진의 참조가치가 어느 정도인가?>

1편에서는 LMAX 아키텍쳐를 소개하고 (소개했다기에는 부끄러울 정도로 간략하게 소개하고) 그것을 성공 사례의 거울로 한번쯤 비추어보자고 말했습니다.

그럼 제기할 수 있는 한가지 자연스러운 질문은: 거래엔진이 뭐 게임 서버도 아닌데, 뭐 어뜨케 참조하잔 말야? 참조할 가치가 있냐? 가 되겠습니다.

제2편에서는 이 문제들을 대답하기 위해 노력을 해보겠습니다.

간단한 대답은 이렇게 되겠습니다: 그렇습니다, 다릅니다. 거래 엔진은 거래 엔진이고 게임 서버는 게임 서버겠죠?

하지만 게임 서버도 게임 서버와 다릅니다. 실시성에 대한 요구가 높은 게임 서버도 있고 실시성에 별로 요구가 없는 게임 서버도 있습니다. 오케이, 알겠습니다. 보다 의미있는 비교를 위하여 이 글에서는 “게임 서버” == “실시간 게임 서버” 로 한정하겠습니다. 그렇다고 하더라도 실시간 게임 서버가 다 같은 건 아니겠죠? 거래 엔진도 거래 엔진이라고 해서 다 같은게 아닙니다.

무엇보다 유사성을 논할 때 우리는 특성들의 집합을 두고 논해야 하는거지 그것이 뭐라고 “불리는가”에만 의뢰하여 유사성을 판단할 수는 없습니다.

그러면 문제는 아래와 같은 형태로 전환됩니다:

거래 엔진과 실시간 게임 서버는 어떤 면에서 유사하고 어떤 면에서 다를까요?

사실상 거래 엔진과 실시간 게임 서버는 많은 특성들을 공유하고 있습니다:

  • 1. 높은 스루풋을 희망한다. (그 단위는 업계에서 공인 되는 건 여러가지가 있지만 여기서는 “TPS”로 하겠습니다.)
  • 2. 레이턴시를 최소화 하고 싶어한다.

— 1 과 2 의 원인으로 양자는 모두 아래와 같은 특점들을 지니게 됩니다.

  • 3. socket 서버이다.
  • 4. 유상태(stateful) 아키텍쳐를 취한다. (모두 “DATABASE IS DEAD”란 화제에 휩쓸리기 좋은 예제이다. 취소선.)
  • 5. 서버가 request / response 모델로도 구현이 가능하지만 성능과 실시성을 높이기 위해서는 보통 서버에서 클라에 푸시하는 방식도 있기를 원한다.
  • 6. IO boundary. (게임 서버의 경우 CPU boundary 일 수 있는데 이는 뒤에서 논하겠다)

— 그리고 semantic 각도에서

  • 7. 리퀫에는 “술어”(predicate) 가 포함되는 경우가 허다하다.

이는 아래의 공통된 특성을 초래합니다:

  • 8. C / S 통신의 내용에는 RPC 가 많다.

— 그리고 

  • 9. 그 술어들에는 상태 의존적인(state-dependent) 술어들이 많다.

— 이는 또:

  • 10. 패킷의 순서성이 보장되여야 하는 경우가 많다.

란 공통 특성을 초래하게 됩니다.
다른 점들도 좀 있습니다. 이를테면:

  1. 거래 엔진은 게임 서버보다 상태변경의 전파성이 강하다. (쉽게 풀이하자면 상태변화의 영향이 보다 글로벌적이다) 이로 인해 상대적으로 마켓 엔진은 게임 서버보다 Partition Tolerance 가 약하다. (게임 서버한테는 다행인 일이겠죠) (AOI가 모던 게임 서버 아키텍쳐에서.. 취소선.. 이건 다른 한 편에서 다룰 화ㅈ.. 취소선)
  2. 게임 서버의 경우 (분산식) 트랜잭션이 깨진 경우에 대해 발견시 후 복구 하는 방법도 흔히 취하는 반면에, 거래 엔진의 경우 트랜잭션의 완정성과 정확성에 대한 요구가 훨씬 크리티클하다.
  3. 거래 엔진의 경우 술어들의 특성상 CPU 밀집형 타스크들이 별로 없다. 즉 데이터 액세스와 간단한 계산이 주된 업무이다. 반면 게임 서버의 경우 어떤 게임인가에 따라 케이스 바이 케이스이겠지만 가끔 CPU 밀집형 계산이 많을 수도 있고 심지어 GPU 밀집형 계산 타스크가 많을 수도 있다. IO bounday 인 경우가 대부분이지만 아닌 경우도 있다.

— 1번 차이점과 2번 차이점은 모두 분산과 관련된 화제들인데, 문제 토론의 집중성을 위하여 이 글에서는 분산에 관련된 점들을 가급적 취급하지 않겠습니다.

3 번 차이점에 대해서는 제대로 풀어볼 필요가 있습니다.
연재의 다른 한 편에서 풀어낼 예정이지만, 저는 유상태 서버를, 취소선, 서비스를 “무상태 서비스를 분석하는 방법으로” 접근할 것을 많이 선호합니다. 그래서 아래와 같은 이미지를 하나 만들었습니다:

유상태 서비스

유상태 서비스 (이미지 출처: 인터넷)

일단 오른 쪽 부분은 무시해 주시기 바랍니다.

어떤 것이 무상태 서비스를 분석하는 방법일까요?

서비스를 아래 두가지로 나누어 보는 것입니다:

  1. 서비스의 상태.
  2. 상태 변경자.

(“Algorithms + Data Structures = Programs” — Niklaus Wirth [1] ) 

유상태 게임 서버를 짜보진 않았지만 무상태 게임 서버를 짜본 개발자 분들도 이 모델 익숙하실겁니다. 놀라운 사실은 유상태 서비스도 무상태 서비스와 본질적으로 다르진 않다는 것입니다!

싱글 스레드 모델의 장점은 여기서 한눈에 나타납니다. 무지 간단하다는 것입니다. BLP가 다른 워커(worker)의 눈치를 보지 않아도 됩니다. 협업 할 필요가 없습니다.

그러면 멀티스레드가 필요 없는건가요?

아닙니다. 하나의 워커가 소화해내기 어려운 계산 타스크가 있다면 워커를 병렬로 돌리고 싶겠죠? 게임 서버의 경우 이런 CPU boundary 인 경우가 있을 수 있다는 것입니다.

Conclusion

그래서, 게임 서버에서도 싱글 스레드로 갖고 성능이 안나오면 그냥 멀티스레딩 해버리면 되지요, 인건가요?

그것을 챌린지 해보자는게 본편의 중심 취지입니다. (본 시리즈의 중심이 아니라면)
힘들고 더러운 작업들 중 병렬화가 쉬운 작업들이 있습니다. BLP (Business Logic Processor) 의 간단성, 그리고 무엇보다도 독재성! 을 위해서는 병렬화하기 쉬운 부분을 “외주”시키자는 것입니다. (이것 역시 Bitshares가 얻은 결론이기도 합니다.)

여기서 외주의 방식과 “외주”행위 자체의 성능 오버헤드 등 구체적인 trade-off 들이 있고 무엇보다도 “외주”와 통신하는 자체가 아래의 제일 큰 문제를 초래할 수 있습니다.

결국 멀티 BLP 이든, 아니면 싱글 BLP 에 하나이상의 “외주”를 두든, 여기서 부터 판도라의 상자를 열어제끼게 됩니다:

동기화!

동기화에 대한 화제는 다음 편에서 다루겠습니다.
(주말 막바지에 급하게 쓰느라 잘 못 썼네요. 제3편은 좀 고민을 해서 좀 잘 써볼까 합니다. 제3편은 늦어질 수도 있습니다. 기대하지 마십시오 ^_^)

References:

[1] https://en.wikipedia.org/wiki/Algorithms_%2B_Data_Structures_%3D_Programs

Advertisements

댓글 남기기

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s