모듈화, End-to-end 원칙, 그리고 Fate-sharing

게임 서버처럼 규모가 큰 시스템을 구현할 때, 우리는 일반적으로 관련된 기능을 묶어서 다루게 된다. 그리고 그걸 “모듈 (Module)” 이라고 부른다. 시스템 모듈화는 처음 시스템을 만들 때 모듈단위의 점진적 구현이 가능하게 한다는 점도 있지만, 추후 시스템 갱신 시 손 대야되는 것의 범위를 한정해준다는 점에서 더욱 중요하다. 다시 말해 시스템 기능이 변경 될 때 전체 시스템을 모두 바꾸는 것이 아니라 관련된 모듈만 교체할 수 있는 것이다.

이때문에 모듈은 단순히 관련된 기능의 집합으로서 “적당히” 모아 두는 것이 아니라, *잘 정의된 (well-defined) 최소한의 (minimal)* 인터페이스를 갖도록 구현되어야 한다. 그래야만 모듈이 변경될 때 미치는 영향을 최소화할 수 있고, 모듈 자체도 인터페이스를 유지하는 한 내부 구현에 대해 자유도를 가질 수 있다. 이런 이유로 모듈화는 시스템 구현에 있어서 중요한 원칙 중 하나이다.

그런데 모듈을 단순히 코드 단위로 묶이는 물리적 단위로 생각하는 것보다 프로세스나 기계 혹은 이들의 클러스터 단위로까지 묶일 수 있는 논리적 단위로 생각하는 것이 좋다. 그러니까 같은 서브디렉토리 아래 있는 소스 파일들이나 라이브러리 뿐만 아니라, 특정 유사 기능을 묶어서 프로세스나 기계로 나눠주는 것도 모듈화다. 예를 들면 우리가 게임 서버에서 구글 플레이 인증을 검증하기 위해서 구글 서버에 REST API 로 접근하게 되는데, 이때 API 를 받는 것이 하나의 프로세스일지 한대의 기계일지 기계들의 집합일지 알 수 없지만 (물론 구글이 처리하는 요청량을 볼 때 당연히 기계들의 집합일 것이다) “인증 검증” 이라는 특정 기능을 전담하는 구글 서버는 그것 자체로 하나의 서비스로도 이해될 수 있고, 전체 “게임 시스템”이라는 측면에서는 외부 모듈로도 이해될 수 있다. 그리고 REST API 가 앞에서 설명한대로 그 모듈의 인터페이스고 우리는 구글이 그 인터페이스를 바꾸지 않는 한 구글 엔지니어가 인증 처리를 더 효율적으로 하기 위해서 내부에서 코드를 바꾸는 것에 개의치 않는다.

그러면 모듈은 어떤 단위로 나누는게 좋을까? 그리고 기계단위로까지 모듈이 흩어질 수 있다면 모듈은 어디서 구현되어야 할까? 그리고 모듈이 상태값 (state) 를 가져야될 때 그건 어디에 저장되어야 될까?

모듈을 어떤 단위로 나눌까 하는 문제에 있어 컴퓨터 네트워킹 쪽에서 주로 사용하는 방법은 “계층 (layer)” 단위로 나누는 것이다. 우리가 사용하는 TCP/IP 역시 이 계층화 원칙에 따른 결과물이다. HTTP 로 게임 서버와 통신하는 것을 예로 들어보면, 1) 802.11a/b/g/n/ac 이라는 물리 규격 계층 위에 2) 안정적인 전송을 보장하지는 않지만 최대한 보내려는 노력은 해주는 (best-effort) IP 계층이 존재하고, 3) 그 위에 전송의 안정성을 보장하는 TCP 계층과 4) TCP 연결이 끊기더라도 사용자 정보를 유지할 수 있게 하는 HTTP 세션 계층, 5) 끝으로 그 위에 게임 서버-클라이언트 프로토콜이라는 게임 애플리케이션 계층이 존재하는 것이다.

그럼 네트워크에서 하나의 프로토콜 계층이 하나의 모듈 단위라면, 앞에서 말한 “잘 정의된 최소한의 인터페이스” 라는 건 어디에 존재하는 걸까? 정답은 프로토콜 헤더다. 상위 계층은 하위 계층의 기능을 이용하기 위해서 하위 계층의 헤더를 채워야하고 (아마 어떤 독자는 그런 헤더를 직접 채운 적이 없어서 의아해할 수도 있는데, 여러분이 사용하는 소켓 라이브러리가 이 헤더를 채워주고 있다), 하위 계층의 헤더에 존재하지 않는 건 쓸 수 없다. 일부 프로그래머들은 프로토콜 헤더를 통신을 위한 단순한 더미로 인식하고 있는 경우가 많다. 그러나 프로토콜에 있어서 핵심은 헤더라고 해도 과언이 아닐 정도로 특정 프로토콜이 할 수 있는 것과 할 수 없는 것은 헤더 포맷으로 결정 되며, 최대한 단순한 인터페이스라는 측면에서 프로토콜 헤더에 새로운 필드를 추가하는 것은 신중해야된다.

그럼 프로토콜 헤더에 뭔가를 추가하는 것이 신중해야된다고 했는데, 넣어도 되고 안되고는 어떤 기준으로 판단할 수 있을까? 이 기준의 좋은 예가 “End-to-end 원칙”이다. 양쪽 끝단 (end) 이 통신하면서 어떤 기능이 필요하다고 했을 때, 아랫쪽 계층에 그 기능이 들어가도 되는지에 대한 가이드라인인데, 이 원칙은 관점에 따라 여러 방식으로 표현되고 이해될 수 있다.

먼저 해당 계층이 자기의 힘만으로 그 기능을 완벽하게 구현할 수 없다면 그 기능을 넣어서는 안된다. 즉, 불완전한 기능을 넣어서는 안된다는 뜻으로 이해할 수 있다. 불완전한 기능은 쓰기에 부적합해서 어차피 양 끝단 (상위 계층)에서 같은 기능을 직접 만들어야 되는 상황이 발생하기 때문이다.

다른 관점으로는, 만일 해당 기능이 양 끝단 (상위 계층) 에서 충분히 만들 수 있다면 아래 계층에 그 기능을 넣지 말아야 한다. 그러니까 아래 계층은 윗 계층이 할 수 있는 일을 대신하는게 아니라 할 수 없는 일에 집중해야된다는 뜻이다. 다만 예외적으로 높은 성능 향상이 있는 경우는 아래 계층에 포함 시킬 수 있는데, 이때도 해당 기능을 쓰지 않는 다른 상황이 부가적인 오버헤드를 겪어서는 안되야만 한다.

이 원칙은 네트워크 통신의 계층화 뿐만 아니라 다른 경우에도 적용해 볼 수 있는데, 예를 들어 여러 게임에 사용될 공통 라이브러리를 만든다고 했을 때, 어떤 기능이 그게 사용될 게임의 특성을 알아야만 하는 것이라면 일반적으로는 라이브러리에 포함되서는 안되고, 개발 시간 단축이나 라이브러리 내부 구조를 모르고 만들 때 보다 큰 성능 향상의 메리트가 있다면 고려해볼 수 있다. 다만 해당 라이브러리를 쓰는 다른 게임들이 그 기능 때문에 라이브러리를 쓰는게 복잡해지거나 성능 하락을 겪어서는 안된다.

End-to-end 원칙은 네트워크 계층 설계에 핵심적인 원리지만, 사실 현실 세계에서는 이 원칙을 의도적으로 무시하는 경우들이 존재한다. 예를 들어 우리가 사용자들이 가끔씩 광고를 보는 조건으로 무료 WiFi 를 제공한다면 광고를 끼워 넣기 위해서는 양 끝단의 통신 (end-to-end) 에 무선 공유기가 끼어들 수 있어야할 것이다. 또한 우리가 게임을 서비스할 때 로드밸런서를 사용한다면, 게임 클라-서버의 통신이라는 end-to-end 중간에 로드밸런서라는 장비가 끼어든 것이기도 하다. 비슷하게 보안장비나 torrent 트래픽을 별도로 처리하기 위한 QoS 장비들 역시 end-to-end 를 의도적으로 깨는 경우다. 그러나 이런 것들은 end-to-end 원리가 무의미하다는 뜻이 아니라, 커뮤니케이션이라는 본연의 목적 외에 관리 목적 때문에 의도적으로 위반하고 있는 것으로 이해하는 것이 맞을 것 같다.

끝으로 분산 환경에서 상태값(state)은 어디에 저장하는 것이 좋을까? 이에 대한 가이드라인이 Fate-sharing 원칙인데, Fate-sharing 원칙은 상태값을 이용하는 객체가 상태값을 관리하게 해서 둘이 운명을 공유하게 (fate-sharing) 해야된다는 뜻이다. 이는 해당 객체가 죽는 경우 상태값을 지워줘야 되는데, 만일 상태값이 다른 곳에 저장 된 경우, 해당 객체가 죽었는지 살았는지를 계속 확인하지 않는 한 죽은 객체의 상태값이 남을 수 있기 때문이다. 우리가 모바일 게임을 개발해서 TCP/IP 연결을 맺더라도 이 연결에 대한 소켓 정보는 게임 서버와 스마트폰의 OS 레벨에서 저장이 될 뿐 중간에 거치게 되는 라우터에는 저장이 되지 않는 것도 이 원칙에 의한 것이다. 그리고 이처럼 중간에 있는 객체가 상태에 대한 정보를 가지고 있지 않으면, IP 가 중간에 포워딩 경로를 바꿀 수 있는 것처럼, 양 끝단 (end-to-end) 은 중간에 커뮤니케이션 경로를 바꾸는 등 자유도가 높아질 수도 있다.

만일 Fate-sharing 원칙을 적용하기 어려운 경우 대안으로 저장되는 상태값을 soft-state 로 유지하는 방법도 가능하다. Soft-state 는 상태값이되 일정 시간이 지나면 사라지는 상태값으로써, 상태값을 쓰는 객체가 주기적으로 상태값을 갱신하게끔 해서 만일 그 객체가 죽는 경우 일정 시간 뒤에는 상태값이 자동으로 소멸되는 것을 보장한다.

이로써 지난 세번의 컬럼을 통해 시스템 설계에 대한 전반적인 이야기를 했다. 첫 컬럼에서는 시스템 설계에 있어서 우선 순위 설정에 따라 시스템의 모습이 달라짐을, 두번째 컬럼에서는 이 관점에서 TCP/IP 가 어떤 우선 순위로 설계 되었는지를, 그리고 이번 컬럼에서는 시스템 설계에 적용되는 일반적 원칙들을 살펴보았다. 다음 컬럼에서는 본격적으로 게임 서버에 대해서 이야기 나누도록 하겠다.

아이펀팩토리 문대경 대표

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중