공룡, 생존, 개척. 독특한 게임 소재로 출시 이전부터 이목을 끌었던 <야생의 땅: 듀랑고>. 기대가 높았던 탓일까? 게임은 서비스 첫날부터 유저 과밀화, 대기 서비스 오류 등 다양한 서버 문제로 인해 유저들의 입방아에 오르기도 했다.
<야생의 땅: 듀랑고>의 오랜 시간 서버 장애에는 어떤 비하인드가 있었을까? 출시 초기 발생했던 다양한 서버 장애들과 그 배경엔 어떤 이유가 있었는지 왓 스튜디오 이흥섭 테크니컬 디렉터의 이야기를 직접 들어봤다. /디스이즈게임 김지현 기자
이흥섭 디렉터는 강연에 앞서 여러 가지 용도로 쓰이는 '서버'라는 단어에 대한 세부적 정의를 내렸다. '서버'란 클라이언트가 서비스를 이용하기 위해 접속하는 프로그램이다. 온라인 게임에서는 대중적으로 '1서버', '2서버' 같이 서로 격리된 세계를 서버라고 부른다.
장르에 따라서는 서버 구분을 두지 않는 온라인 게임도 있지만, 대개의 경우 통신 속도나 서버 성능 상의 이유로 서버를 나눠 둔다. 플레이어끼리 서로 상호작용하는 멀티플레이 요소는 같은 서버 안에서만 이뤄지기 때문에 서버가 다른 친구와는 직접적인 상호작용을 할 수 없거나 상당히 제한받는다.
'노드'는 서버 프로그램을 구동하는 프로세스다. 트래픽이 적고 목표가 간단한 서버의 경우, 단일 노드로 처리되는 경우도 있지만 클라이언트 트래픽이 많거나 목표가 복잡한 서버에서는 클라이언트와 직접 통신하는 노드 외에도 다양한 노드가 서로 연결돼 협력하는 구조를 가진다.
이런 노드를 돌리는 컴퓨터를 '호스트'라고 부른다. 호스트는 데이터센터에 직접 구축한 물리적 형태의 컴퓨터일 수도 있고, 클라우드 인프라에서 돌아가는 가상 컴퓨터일 수도 있다. 한 호스트에는 사양에 따라 여러 개의 노드를 띄울 수도 있다.
하나의 서버에는 게임 서버 애플리케이션 외에도, 데이터베이스(DB)나 메시지큐(MQ)같은 여러가지 부품이 유기적으로 맞물려 돌아간다.
데이터베이스: 여러 응용 시스템의 통합 정보들을 저장해 운영하는 데이터 묶음
메시지큐: 유저가 키보드나 마우스로 명령한 것을 메시지 형태로 변환해 저장하는 프로그램
<야생의 땅: 듀랑고>는 모바일 샌드박스 MMORPG다. 게임 세계에 보이는 대부분의 요소가 서버 사이드에 저장돼 있고, 플레이어의 행동 하나하나가 게임 세계에 영구적인 영향을 남긴다. 유저들은 눈앞에 보이는 땅을 사유화하고, 자기만의 집을 짓거나 마을을 설립할 수 있다.
MMORPG에선 한 곳에 너무 많은 유저가 모이면 생기는 문제들을 해결하기 위해 '채널'이라는 장치를 넣는다. 수많은 유저들이 게임 속 세계에서 같은 장소에 있다고 해도, 같은 채널에 있는 유저끼리만 서로 동기화되고, 상호작용할 수 있도록 제한하는 일종의 장치다.
하지만 <야생의 땅: 듀랑고>는 게임 특성상 채널을 넣을 수 없다. <야생의 땅: 듀랑고> 세계에선 대부분의 상호작용이 땅에서 이뤄진다. 다른 채널에서 일어난 변화가, 내가 있는 위치에 원격으로 변화를 일으킨다면 인과관계 파악이 안 되고, 상황을 쉽게 납득할 수 없을 것이다.
<야생의 땅: 듀랑고>팀은 대규모 샌드박스라는 목표를 위해 단일 채널을 추구했다. 거기에 한 발 더 나아가 그들은 하나의 서버만 운영하길 추구했다. 이흥섭 디렉터는 서버 선택에 대한 고민을 없애고 싶었던 것을 그 이유라 설명했다.
결국 단일 서버는 실패했지만, 그것을 구현하기 위한 노력들로 인해 한 서버의 유저 수용량은 기존 MMORPG에 비해 높은 수준으로 만들어 낼 수 있었다.
<야생의 땅: 듀랑고> 팀에게는 서버 개발에 있어 높은 가용성이라는 또 하나의 목표가 있었다. 동시 접속이 치솟거나, 인프라에 장애가 발생하더라도 서비스가 멈추는 일은 아예 벌어지지 않거나 적어도 빨리 복구될 수 있도록 해야 했다. 그래서 이들은 동급의 노드를 추가해 서버를 수평 확장하는 방식을 사용했다.
하지만 노드가 늘어나는 만큼, 서버 장애가 발생할 확률도 높아진다. 몇몇 노드가 죽더라도 서비스에 큰 지장이 없게 만들려면 SPOF가 단 하나도 없도록 설계하거나, 최소한 장애가 났을 때 자동으로 신속하게 복구되게끔 만들어야 했다.
단일고장점(SPOF): Single Point Of Failure의 약자로 시스템 전체를 다운시키는 하나의 고장 요소
개발팀은 게임 서버의 SPOF를 최소화하고, DB는 장애로부터 빠르게 복구될 수 있는 방향의 기술을 선택했다. 성공한다면 서버를 패치할 때 전체 서비스를 중단시키는 대신, 일부 노드부터 새 버전을 확산시키는 방법의 무중단 패치도 가능할 것이며, 나아가 부하에 따라 증설과 감축도 자동으로 할 수 있겠다는 것이 그들의 생각이었다.
# 초반 서버 장애의 원인: 데이터 모델의 치명적 오류 & 인구밀도 조절 실패
<야생의 땅: 듀랑고>의 초반 서버 장애의 큰 원인 중 하나는 데이터 모델의 치명적 성능 문제다. <야생의 땅: 듀랑고>에서 사용되는 여러 데이터 베이스 중 가장 중요한 것은 카우치베이스(Couchbase)다. 카우치베이스는 게임 세계를 구성하는 거의 모든 정보들을 한 군데에 저장하는 역할을 한다. 카우치베이스로 보내진 데이터는 여러 노드에 나뉘어 저장되며 각 노드는 이웃 노드의 데이터를 복제해두고 있기 때문에, 특정 노드에 장애가 생기더라도 서비스 장애는 빠르게 자동 복구된다.
카우치베이스는 저장 문서가 JSON 형식인 경우에 한해 저장소 이외에도 부가적인 기능을 몇 가지 쓸 수 있다. 데이터를 읽고 쓸 때 문서를 통째로 다루는 대신 내용의 일부분에만 접근할 수도 있고, 문서의 내용으로 키를 찾을 수 있게 해주는 여러 가지 색인과 검색 기능을 쓸 수도 있다.
이처럼 카우치베이스는 저장과 조회 기능의 활용도가 높고 사용하기 간편하다. 반면, 색인과 검색 쪽 기능은 보다 구체적인 목표를 셋팅했을 때 시스템 과부화가 걸리기 쉬우며, 이는 치명적인 성능 문제로 이어질 수 있다. 실제로 이 문제는 <야생의 땅: 듀랑고> 출시 직후 있었던 장애에서 큰 지분을 차지했다.
출시 초반 서버 장애의 두 번째 주요 원인인 인구밀도 조절에 대한 이야기로 넘어가 보자. 채널 방식을 쓰지 않는 <야생의 땅: 듀랑고>는 유저를 적절히 분배하기 위해 '공간' 즉, 섬을 늘리는 방법을 사용했다.
각 섬의 디자인에는 적절한 인구수를 정해두고 있다. 한두 명 만을 위해 수 백 명짜리 섬을 통째로 만들어내면 자원이 과잉 공급되고, MMORPG만의 북적거리는 재미도 느낄 수 없을 것이다. 반면 한 섬에 너무 많은 유저가 몰리면 서버도 클라이언트도 모두 과부하에 걸려 제대로 된 게임 플레이를 즐길 수 없을 것이다.
땅이라는 한정된 자원을 수많은 유저가 같이 공유하다 보면 경쟁이 과열될 수도 있다. 인구 부족과 인구 과밀 사이에 있는 가장 적절한 인구밀도를 맞춰줘야만 적당히 다른 유저들과 부대끼면서도 쾌적한 게임플레이를 이어나갈 수 있다.
하지만 폭발적으로 늘었다가 줄어드는 유저와 달리, 섬은 한 번 늘리면 쉽게 줄일 수 없다는 한계가 있다. 이미 유저들이 세운 집과 사유지가 있어 섬을 폐쇄하기 어렵다. 게다가 새롭게 섬을 생성한다 해도 사유지와 집을 세운 유저들이 알아서 이동하는 것을 아니기에, 이미 인구가 밀집한 섬을 쾌적하게 만드는 것은 불가능에 가까웠다. 그렇기에 이로인해 발생한 서버 이슈 역시 해결하기 쉽지 않았다.
섬의 인구밀도를 맞추는 데에는 '인구 분배기'라는 장치가 쓰였다. 이 장치는 유저를 적절한 섬에 배정해주는 역할을 한다. 배정할 섬이 없으면 여기서 새로운 섬이 만들어지기도 한다. 그런데 인구 분배기는 유저가 다른 섬을 찾을 때에만 개입한다.
그렇기 때문에 <야생의 땅: 듀랑고>는 인구가 과밀해진 섬을 구원할 방법이 마땅치 않고, 한 번 만든 섬을 쉽게 회수할 수 없다 보니 새로운 섬을 적극적으로 만들지 못한다는 큰 제약을 갖고 있었다.
모든 섬은 면적과 무관하게 청크라는 균일한 조각으로 나뉘어 있다. 이렇게 나뉜 청크는 여기저기에 쓰인다. 섬 전체에서 벌어지는 여러 상황 중, 어느 정도를 클라이언트에 스트리밍할지도 청크 단위로 결정한다.
한 노드가 처리하는 공간 단위 역시 섬이 아니라 청크다. 섬 전체를 통째로 처리하는 대신, 몇몇 청크만 처리하기 때문에 섬이 아무리 넓어도 감당할 수 있는 것이다. 청크마다 전담하는 노드가 정해져 있지는 않다. 반대로 노드가 스스로 자기가 맡을 청크를 고르게 돼있다.
이 때 노드와 청크는 1:1 관계가 아니라 N:N 관계다. 한 청크를 한 노드가 전담하지 않고, 여러 노드가 나눠서 처리하는 것이다. 어떤 노드든 자기가 원하기만 하면 아무 청크라도 맡을 수 있다. 이렇게 한 청크를 여러 노드가 나눠 처리하는 건 가용성을 높이는 데 도움이 된다. 한 노드가 죽더라도 재접속만 하면 게임을 바로 이어갈 수 있기 때문이다.
이때 같은 청크를 맡은 노드끼리는 RPC로 서로의 상태를 동기화한다. 이런 노드 간 RPC엔 비동기 메시징 패턴 중 하나인 Pub/Sub 패턴이 쓰인다. Pub/Sub 패턴에는 발행자와 구독자가 있다. 발행자는 메시지를 쏘는 쪽으로 개별 구독자에게 직접 쏘는 대신에 특정 채널에 쏘게 된다. 만약 아무 구독자도 그 채널에 관심이 없다면 메시지는 조용히 버려진다. 구독자는 채널을 구독할 수 있다. 구독자가 채널을 구독하면 그때부터 메시지가 구독자에게 전달된다.
<야생의 땅: 듀랑고> 서버의 각 노드는 모두 구독자이자 발행자다. 채널을 매개로 서로 자유롭게 메시지를 주고받을 수 있다. 노드 간 RPC의 재료 이는 노드 간 RPC의 재료로 쓰인다. 각 청크는 하나의 Pub/Sub 채널이다. 같은 청크를 처리하고 있는 노드끼리는 그 청크의 채널을 통해서 서로 RPC를 주고받고 필요한 정보를 동기화한다.
이런 방법으로 한 청크를 여러 노드로 나눠서 처리할 수 있다. 이때 다른 노드에 접속한 유저는 고스트로 만들어져서 같이 동기화된다. 어떤 노드에서 누군가 집을 짓는다면 모든 노드에서 똑같이 이 인과관계를 파악할 수 있다.
하지만 명백한 한계는 있다. 한 청크에 몰려있는 유저를 여러 노드로 나누면 게임플레이 로직 부하는 분산시킬 수 있지만 각 노드가 그 청크에서 동기화해야 되는 유저의 수가 줄어들진 않는다는 것이다. 게다가 노드들끼리 고스트를 동기화하는 데에도 어느 정도는 비용이 든다.
그래서 한 청크를 얼마나 많은 노드가 나눠서 처리하느냐에 따라서 부하 양상이 달라진다. 한 청크를 한 노드가 전담하게 설정하면 동기화 부하는 없어지지만 게임플레이 로직 부하가 늘어난다. 반대로 한 청크를 너무 많은 노드에 분산시키면 동기화 부하의 비중이 커진다.
그 사이에서 부하의 총합을 낮출 수 있는 적절한 지점을 찾는 것은 매우 어렵다. 그 균형점을 찾는다고 해도 한 청크에 너무 많은 유저가 몰리면 더 이상 제대로 된 성능을 내긴 어렵다. 그렇기 때문에 인구 분배기의 역할은 굉장히 중요했다.
# 무중단 패치 미도입 이유는 서버에서 발생한 '공멸현상'
# <듀랑고> 출시 후 겪은 크고 작은 문제들의 원인, 그리고 해결방안
쿼리: 데이터베이스에 정보를 요청하는 것
Redis: '키-값' 구조의 비관계형 데이터를 저장하고 관리하기 위한 NoSQL의 일종