게임과 웹의 캐시 패턴 차이점

2023. 3. 29. 00:03코딩/잡 공부

게임과 웹의 캐시 패턴 차이점

  1.  웹
    • 사용자에게 보낼 HTML 캐시
    • API 파라미터에 대한 동일한 응답을 위한 캐시
    • DB 처리 전의 중간 저장소로의 활용.
    • 동일한 URL의 정적 데이터 (이미지, js, css 파일 등)에 대한 캐시
    • 메모리에 임시 데이터를 저장하는 경우가 매우 드뭄.
      • Ehcache 등을 통한 캐시가 그런 방식 아니냐라고 물어볼 수도 있지만, 게임에서의 메모리 활용과는 궤를 달리하는 측면이 큼.
    • 서버의 스케일 인/아웃의 유연함을 위해, API 호출이 특정 서버로만 전달되게 구성하는 경우가 적다.
      • 이는 메모리에 캐싱 해둔 데이터가 과거 데이터 일수 있음을 감안해야 할 수 있거나, 이를 동기화 하는 별도의 추가 작업이 필요함을 의미한다.
      • 또한 session의 경우 만료 시점에 캐싱 해둔 데이터를 DB에 반영한다거나 이에 대한 대비를 하는 작업은 웹 관점에서도 데이터 유실의 리스크가 있기때문에 쉽게 선택하기 어려운 측면이 있다.
      • Session 만료 체크를 polling 하기도 껄끄러울 뿐더러, 캐싱하던 session 데이터를 서버 종료 시점에 push 해주어야 하고, 서버 크래시라도 발생한다면 게임 쪽에서 종종 발생하는 백섭 같은 데이터 유실로 이루어 질 수 있기 때문이다.
    • 분산 캐시는 어떠한가?
      • 특정 서버에서의 변경된 내용으로 인해 캐싱 내용이 변경 될 경우, 이를 연관 서버들에게 전파해, 캐시 데이터를 다시 동기화함을 말한다.
      • 조금은 늦게 캐시가 동기화 되어도 문제 없을 법한 것들 위주로 캐싱한다.
      • 조금의 오차도 용납되지 않을 중요 데이터일 경우 어차피 웹에서도 캐시를 쓰기 어렵다.

 


  1. 게임
    • 웹 서버를 이용하지 않는 경우를 전제함.
    • 이미 메모리에만 들고 있는 임시 데이터가 존재하는 경우가 대 다수.
      • 주로 소켓과 물려있는 메타 정보를 저장하는 세션에 저장.
      • 세션끼리 공유하는 데이터들을 메모리에 보유.
        • 필드 (=Zone)
        • 채널
    • 동일한 요청이라고해도, 이미 바뀌었을 가능성이 매우 높기에 동일한 요청 파라미터라해도 캐싱하는 것이 무의미하다.
      • 가능한 컨텐츠는 일부 존재 할 수 있을 듯.
      • 우편함이라거나, 인벤토리 같은 진입점을 제어하고, 진입점 변경시 캐시 클리어 방식으로 웹과 비슷하게 사용 가능할 것 같다.
    • 게임은 브로드 캐스트를 자주 사용하는 편인데, 이벤트가 발생하면 새로운 요청을 클라이언트가 보내는 것이 아니라, 서버가 적합한 대상자에게 브로드 캐스트함으로써 실시간 동기화해야 하기 때문이다.
      • 특히 이동, 전투 등의 과정에서 ms 단위의 동기화가 어긋나도 버그라고 느끼는 게임에서는 캐싱할 수 있는 정보고 극도로 한정되어있다.
    • 그렇다면 과연 게임에서의 캐싱의 주 사용처는?
      • 메모리에 들고 있는 내용은 언젠가 DB에 반영되어야 할 데이터기 때문에, DB 저장 전의 중간 저장소로 사용하는 경우가 많다.
        • DB 처리 속도는 게임의 데이터 변화량보다 느릴 때도 많기 때문에, 메모리 연산을 통한 응답을 주어야만 반응성을 유지시킬 수 있기 때문이다.
      • 캐싱 용도로 저장된 데이터를 공유하는 용도로도 쓰인다.
        • DB 억세스를 직접하면, Read IO만으로도 DB에 부담을 줄 수 있다.
        • 적절한 키로 분배해놓은 캐시 서버로부터 데이터를 얻어옴으로써, DB에 부담을 줄이는 선택을 한다.
      • 심지어는 중국이나 국내 일부 업체는 DB의 트랜잭션 같은 처리를 전혀 사용하지 않고, 캐시 서버, 어플리케이션 서버 등에서 철저히 제어함으로써의 성능 극대화마저 노릴 정도니 접근 자체가 완전히 다르다고 해도 무방하겠다.

 

 

 

 


모바일 게임 서버의 경우 웹서버를 게임서버로 이용하는 경우가 많지만 그렇다고 일반적인 웹서비스와 동일하게 구성하기엔 몇몇 문제점들이 뒤따른다. 여기에서는 그 중 쓰기 문제에 대해서 다뤄본다.

매우 많은 쓰기 작업

일반적인 웹서비스와 가장 큰 차이가 무엇인가라고 질문을 받는다면 나는 주저없이 쓰기 작업의 빈도 수라고 대답한다. 웹서비스의 경우 조회:쓰기의 비율이 높게 잡아도 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 서버군에 장애가 발생했을 경우의 해당 서버에 적재되어던 쿼리는 실행되지 못하고 사라질 것이다. 이 경우의 대비책을 마련해야 하는데 해결하기 매우 어려운 문제다.

 

 

- 출처

모바일 게임서버 구축 시의 쓰기 문제 (bravenamme.github.io)