##
모바일 게임 서버의 경우 웹서버를 게임서버로 이용하는 경우가 많지만 그렇다고 일반적인 웹서비스와 동일하게 구성하기엔 몇몇 문제점들이 뒤따른다. 여기에서는 그 중 쓰기 문제에 대해서 다뤄본다.
매우 많은 쓰기 작업
일반적인 웹서비스와 가장 큰 차이가 무엇인가라고 질문을 받는다면 나는 주저없이 쓰기 작업의 빈도 수라고 대답한다. 웹서비스의 경우 조회:쓰기의 비율이 높게 잡아도 10:1 정도일 정도로 조회 요청이 요청의 대부분을 차지한다.
하지만 게임 서비스의 경우, 조회:쓰기 비율은 거의 1:1 에 가까우며 쓰기 작업이 조회 작업의 비율보다 높을 때도 많다. 이로 인해 일반적인 웹서비스와는 다른 전략이 필요하다.
왜 쓰기 작업의 비중이 높은가
게임이라는 카테고리 특성 상 유저가 뭔가 액션을 취하면 그에 관련된 정보를 갱신해야 하는 경우가 대부분이다. 물론 게임 내에서는 조회만 하는 기능들도 많으나 보통 그런 기능들은 게임 클라이언트에서 캐시해서 서버요청 자체를 줄이는 방향으로 개발되기 때문에, 실시간으로 변동될 가능성이 있는 데이터를 조회하는 것 이외에는 서버에서 조회기능만 수행할 일이 드물다.
높은 쓰기 작업 비율이 일으키는 문제
규모가 작은 게임이라고 할지라도 유저가 어느정도 모이기 시작하면 쓰기 작업은 꽤 큰 규모의 웹서비스와 비슷하거나 그 이상의 수준으로 쓰기 작업이 발생하게 된다. 쓰기 작업 자체가 조회 작업보다 훨씬 비싼 작업이기 때문에, 쓰기 요청이 발생할 때 마다 DB에 INSERT나 UPDATE를 해주게 되면 DB가 받는 부담이 매우 커진다. RDB에서 일반적으로 사용하는 Master-slave 구조의 경우 Master에 집중적으로 부하가 걸릴 것이라고 쉽게 예측할 수 있다.
결국 분산 DB를 구축해야 하는데…
샤딩 같은 기법을 이용하여 분산 DB를 어쨋든 구축을 해야한다. 이 경우 어떻게 구축하느냐가 관건인데…
RDB로만 밀어붙이기
오라클이나 MySQL의 경우 자체 샤딩 솔루션을 제공하고 있기 때문에 물량빨로 커버가 가능하다. 그러나 오라클의 경우 비용이 (아주 큰)문제가 되고 MySQL의 경우 NDB 클러스터링 구축과 운영 난이도가 결코 낮지 않다. DB운영을 전담해줄 부서가 있다면 이야기가 다르겠지만…
또는 자체적으로 샤딩을 구현해서 RDB에 분산해서 저장하는 방법을 사용할 수도 있다. 다만 이 경우 데이터가 나뉘어서 저장되므로 데이터 검색에 큰 제약이 걸리기 때문에 이 문제에 대한 대책을 마련해야만 한다.
이러나 저러나 RDB로만 해결을 하기에는 많이 버겁다.
NoSQL로만 밀어붙이기
NoSQL DB는 RDB에 비해 제공되는 기능과 저장 구조가 훨씬 단순하기 때문에 분산 DB구축에 큰 강점을 지니고 있다. 그래서 2010년대엔 NoSQL 붐이 불며 한 때 RDB의 자리를 위협한다는 소리까지 나돌았지만 저장된 데이터 무결성에 있어서 RDB를 대체하기가 어렵다는 것이 치명적인 단점이다.
또한 NoSQL 은 데이터 검색이 큰 문제가 된다. RDB에서 제공해주는 막강한 검색기능을 이용할 수가 없기 때문이다. 하지만 NoSQL을 제대로 경험하지 않았을 경우 ‘저희 NoSQL DB는 검색 기능도 막강합니다’ 라는 홍보에 쉽게 낚인다. 모든 NoSQL 솔루션 회사들이 자사 제품은 검색 기능이 알차다고 홍보를 하지만 실제로 사용해보면 나사가 하나가 아니라 여럿 빠진 경우가 거의 대다수이며 RDB와 같은 수준의 검색은 애초에 기대하지 말아야 한다. 물론 나름 괜찮은 검색을 제공하는 NoSQL DB도 존재하긴 한다만…
이렇게 검색기능이 빈약하기 때문에 검색기능은 직접 구현을 하든 다른 솔루션을 도입을 하든 해서 해결해야 한다. 또한 데이터 저장 구조를 설계할 때 RDB에서 테이블을 설계하는 것 이상으로 신중하게 데이터 구조를 설계할 필요가 있으며, 사용하는 DB에 따라서 그냥 검색기능이 없다고 가정하고 설계해야 할 수도 있다.
RDB와 NoSQL 을 섞어 쓰기
‘데이터의 무결성을 위해 RDB에 저장하고 NoSQL에도 저장해서 운영하자’ 라는 생각에 다다르게 되는데, 이것만 보면 기본적인 RDB - Cache DB 구조와 별 다를게 없다. 하지만 게임의 경우 위에 말했듯이 쓰기가 많다는 것이 문제이므로, 데이터의 갱신이 있을 경우 RDB의 데이터를 먼저 갱신하고 그 후 캐시를 갱신하는 일반적인 RDB - cache DB 전략으로는 문제를 해결하기 어렵다.
그래서 일반적인 캐시 전략과는 순서가 반대가 된다. 즉, 캐시에 먼저 데이터를 갱신하고 그 후 RDB의 데이터를 갱신하는 방법을 취한다.
DB cache 서버
데이터의 갱신이 발생하면 캐시의 데이터는 즉각 갱신해주고, 갱신할 데이터들은 모아서 RDB에 일괄로 갱신해주는 방법을 취하면 RDB의 부담을 크게 경감할 수 있다. 고민하다보면 자연스럽게 이 결론에 도달하게 되지만 실제 구현은 만만치 않다는게 문제다. 과거 이 역할을 맡는 서버를 구상하면서 부딪혔던 문제들을 간단하게 정리해보자면
일괄 UPDATE 문제.
INSERT 는 일괄로 처리하기가 쉬우나 UPDATE는 그렇지 않다. CASE 문을 이용한 꼼수나 원트랜잭션에 밀어넣기, 혹은 임시 테이블을 활용한 방법등등을 동원하여 UPDATE를 일괄로 처리할 수 있는 방안을 찾아내어야 한다.
UPDATE 순서의 보장
실제로 DB cache 서버를 구상하게 된다면 가용성을 위해서나 성능을 위해서나 한대로 구성하는게 아니라 여러 대로 구성하게 된다. 만약 동일한 테이블에 업데이트 하는 서로 다른 쿼리가 각각의 DB cache 서버에 적재되었을 경우 어떻게 UPDATE 순서를 보장할 것인가에 대한 고민이 필요하다. DB cache 서버마다 특정 테이블만 담당하도록 설계를 한다거나(물론 이 경우는 또 다른 문제가 발생한다), 메시지 큐등을 이용하여 쿼리 순서를 보장하는 방안도 생각해볼 수 있다. 다만 어떤 방안을 채용하던간에 쿼리 순서를 보장하는 것이 쉽지는 않다.
데이터 무결성의 보장
DB cache 서버군에 장애가 발생했을 경우의 해당 서버에 적재되어던 쿼리는 실행되지 못하고 사라질 것이다. 이 경우의 대비책을 마련해야 하는데 해결하기 매우 어려운 문제다.