게임 서버를 웹 서버와 같이 쓰기

Posted by 엘키의 주절 주절 on January 24, 2022

개요

왜 게임 서버를 온전히 웹서버로 구축하기가 아닐까?

바로 여전히 게임은 소켓 서버 (또는 TCP 서버라 부르는 사람도 있지만… 용어적 올바름은 논외로하고, 이 글에서는 소켓 서버라고만 사용하겠다)가 필요하고, 이 소켓 서버라는 것은 웹 소켓과 같은 데이터 릴레이의 용도가 아닌, 상태 기반 서버가 필요함을 의미한다.

상태 기반 서버(소켓 서버)가 필요한 경우와, 웹 서버로만 구축 해도 되는 경우에는 어떤 경우가 있을까?

상태 기반 서버가 반드시 필요한 경우

  1. 실시간 요소 (인터랙션 요소)가 있는 컨텐츠.
  2. 빠른 응답성이 중요한 경우.

주로, MMORPG, FPS, TPS 등의 게임들이 이 케이스에 해당한다.

웹 서버로만 구축해도 될 경우

  1. 대부분의 컨텐츠가 자신의 데이터에만 접근 할 경우
    • 인터랙션 요소(거래, 실시간 PVP, 실시간 레이드, 실시간 PVE 등)가 없는 케이스.
  2. 일부 컨텐츠가 클라이언트에서 모든 처리가 이뤄지고, 서버는 그 검증만 맡아도 되는 경우
    • 클라이언트의 로직을 서버가 공유한 뒤 후검증 한다거나, 약식 구현한 뒤 후검증 하는 방식을 말한다.
  3. 게임 서버가 단순히 데이터 관리만 하는 경우.
    • 이는 클라이언트 레이어에서의 핵 사용으로 인한 이슈들이 터진 게임들처럼 검증이 없으므로, 핵 이슈가 발생 할 수 있다.

상태 기반 서버가 필요 없는 구성이라면 안 쓰는게 관리 이슈 면에선 좋겠지만, 필요한 장르의 게임을 개발해야 한다면, 웹 서버가 생산성과 관리 이슈 측면에서는 장점이 많기에, 이를 섞어 쓰는 것이 좋을 때가 많다.

웹서버와 소켓 서버는 어떤 관계인 것이 적합할까?

물론 게임 마다 다르고, 개발자마다 생각도 다를 것이다.

상세한 구조는 다양한 모양새를 띌 것이니, 역할을 분담해보자.

웹 서버

  • 대부분의 1인 컨텐츠와 데이터에 적용
  • 일반적인 웹 서버 형태 그대로를 이용

소켓 서버

  • 직접적인 DB 접근 및 처리를 하지 않게 구성
    • 웹서버의 REST API를 통해서 DB 접근
    • 혹은 WebSocket, Socket.IO, SignalR, RabbitMQ 과 같은 웹 호환 통신 방식으로 연결
  • 다인 연관 컨텐츠는 모두 소켓 서버에서 처리

이렇게 규칙을 정했을 때 상대적 장단점은 다음과 같다.

웹 서버

장점

  • 낮은 복잡도를 유지할 수 있어, 실수나 버그의 소지가 적다.
  • 웹 서버의 특징상 메모리에 올라온 데이터 자체는 휘발성이므로, 스케일 인/아웃이 유연하다.

단점

  • DB 동시 접근시, 데이터 무결성이 깨질 수 있다.
    • 스프링 JPA 기준 @Transactional 키워드같은 트랜잭션을 이용할 순 있겠으나, 이는 성능 일부를 희생하는 접근이라서, 게이트웨이 단에서의 분배를 통해 잠금 없이도 무결성을 지켜 주는 방법도 검토해볼 수 있겠다.
  • 게임 서버와의 연동을 목적으로 할 경우, 캐시 사용이 가능한 컨텐츠가 한정되어 있다.
    • 랭킹, 전적 등과 같은 일시적 지연을 감내 할 수 있는 데이터
    • 혹은 캐시 히트률이 떨어지더라도 인벤토리와 같이 크기가 큰 데이터는 시도해봄직 하다
      • 웹의 일반적 캐시 도배 정책처럼 인벤토리 전체를 캐싱할 경우 히트율이 많이 떨어지며, 게임의 컨텐츠 특성과 아이템의 속성값 변화 등에 따라 적절한 범주의 캐싱과 규칙이 필요하다.
        • 그렇다보니 게임에서는 커스텀 캐시 서버가 필요하고, Redis같은 범용 캐시 서버를 쓴다면 위에서 언급한 특화 코딩이 대부분 필요해진다.

소켓 서버

장점

  • 응답성을 빠르게 유지 가능하다.
    • 특히 대부분 소켓 라이브러리는, 패킷을 펌핑해서 로직 레이어 처리까지의 효율을 우선시하며, 이과정에서 병목 현상을 제거해둔 것이 일반적이다.
    • 또한 디비 처리를 비동기로 처리하는 것이 일반적 (동기로 처리하는 케이스도 없는건 아닌데, 이는 실수일 떄가 많고, 렉의 원인이 될 떄가 많다)
  • 다인 컨텐츠 컨트롤이 수월하다.
    • 웹서버에서도 처리하지 못하는건 아니다.
      • 다만, 웹 서버에서도 결국 소켓이나 MQ 계열 등을 사용해야하며, 이를 적정 속도로 끌어올리기 위해선 소켓 서버 처럼 메모리 기반으로 처리를 해야 된다. 결국 소켓 서버나 다름 없어 진다.
    • 메모리보다 빠른 저장 장소는 거의 없다
      • 메모리에 올려둔 데이터로 실시간 처리 하는 것보다 빠른 처리는 거의 없다.
      • 스레드 동기화 문제만 해결한다면, 웹서버에서는 낼 수 없는 퍼포먼스를 보장한다.
        • 물론 스레드 동기화 문제를 해결하며 다인 컨텐츠를 컨트롤 하는 것이 쉽지 않다.

단점

  • 크래시에 민감하다.
    • DB에 저장되지 않은 메모리에 보유한 데이터가 일시적이라도 존재하는 구조다.
      • 결국엔 롤백이 일부분 가능하며 (예를들어 LOL이라면 서버 크래시 발생시 진행중이던 게임이 취소된다거나, 아이템이 날라간다거나, 전적 기록이 안된다거나 등), 이를 방지하기 위해선 중요 데이터의 경우 임시 저장소에라도 기록해두어 롤백이되지 않게 예외처리를 해두어야 한다.
        • 소켓 서버의 예외처리란 결국 웹 서버처럼 디비를 즉각 처리하는 경우와 조금 다른 접근과 처리가 필요함을 말한다.
  • 확장이 어렵다.
    • 메모리에 들고 있는 데이터가 영향을 주므로, 동적 확장이 까다롭다.
      • LOL이나 배그류 같은 매치 기반은 그나마 상대적으로 스케일이 수월한데, MMORPG와 같은 영속성을 기반으로 이어진 경우는 좀 더 까다롭다.
      • 특히 동적 축소는 더욱 까다로운데, 다른 서버로 자연스레 옮겨주고 남아있는 유저를 0으로 만든 뒤에야 서버를 감축 시킬 수 있기 떄문이다.
  • 코딩 난이도가 높다.
    • 다인 컨텐츠를 처리하기 쉽게 하기 위해서는, 공유 데이터를 배치하게 된다.
      • 공유 데이터를 동시 접근하지 않기 위한 여러가지 방법을 고민하게 되는데, 크리티컬 섹션과 같은 동기화 객체는 블러킹 동작이므로, 지연시간을 발생시키는 원인이 되므로, Akka와 같은 액터 기반 메시징 프로그래밍이던, 잘 짜여진 쓰레딩 디자인, 동일한 데이터를 바라보게 하는 readonly 데이터 사본을 복사하는 방식이던 다양한 고난도 (실수하기 쉽고, 코드 작성이 까다로워지며, 디버깅도 어려워지는) 코딩이 되기 쉽다.
    • 지연이 발생하기 쉬움.
      • 발생의 요인도 대부분 DB 처리 시간인 경우가 많은 웹 서버에 비해, 소켓 서버는 좀 더 다양할 수 있다.
        • 위에서 언급한 잠금 객체 이슈나, (이건 웹 서버도 마찬가지지만) 다른 패키지의 함수를 호출했는데 내부적으로 파일 처리 등의 무거운 동작으로 인한 블러킹이 발생하거나 같은 상황이 좀 더 자주 나올 수 있다.
      • 공유 데이터 접근과 패킷 순서에 따른 처리 보장을 위해, 병렬 처리보다는, 직렬 처리를 어딘가에서는 하게되는데, 이로 인해 쓰루풋보다 인풋이 많거나, 특정 처리가 오래 걸릴 경우 전체 지연 시간이 발생하게 된다.
    • 결합도가 생기기 쉽다.
      • 게임의 경우 대부분의 컨텐츠가 상호 연관관계를 갖게끔 의도한다.
      • 캐릭터, 혹은 계정이 게임내의 수십개 컨텐츠와 수만개 데이터와 연관해서 동작한다.
        • 이로 인해 코드도 복잡해지며, 종속 관계, 연관 관계가 복잡해지기 쉽다.
          • 상호 작용하는 대상이 많아지므로, 슈퍼 클래스가 되기 쉽고 버그도 생기기 쉽다.

이렇게 상대적으로 소켓 서버는 단점이 더 많다.


단점을 감안해도, 소켓 서버가 꼭 필요할 때

그럼에도, 해당 장점들이 꼭 필요한 장르의 경우는 대체제가 없기에 사용은 해야 한다. (위에서 잠시 언급한 Akka 기반의 액터 모델은 결국엔 소켓 서버의 단점을 일부 갖게 된다)

그래서 위에서 언급한 대로, 대다수 컨텐츠는 최대한 웹 서버로 코딩하고, 불가피한 컨텐츠나 기능 한정 소켓 서버를 이용하는 방법이 채택 해 볼만한 접근이라고 생각한다.

MMORPG와 같은 연결된 월드가 존재하는 경우에는 소켓 서버와 웹 서버를 병행해서 쓸 포인트가 많지 않다. 가능하다면 상점이라거나, 랭킹, 칭호, 스킬과 상대적으로 정적이며, 월드상 위치나 월드 배치 컨텐츠에 영향을 덜 받는 경우에 써봄직 하다고 할 수 있다. 물론 이 경우에도 웹 서버에서 벌어진 변화를 적절히 게임 서버에 전달해야 되므로, 게임 서버와의 연결과 스레드 동기화 오류 없게끔 제반 구성이 되어있어야 한다.

매치 기반 게임의 서버의 경우 조금은 상황이 나은데, 해당 매치가 벌어지는 서버만 소켓 서버로 만들면 되기 때문이다.

매치 서버에서 진행 중인 데이터 중 결과만 중요하다면, 최초 매치 시작시의 불러오기와, 매치 종료시 저장만 중요해진다.

이정도의 처리라면, 웹 서버와 게임 서버간에 MQPub/Sub기능을 제공해주는 다양한 기술로 정도로도 대부분의 통신이 처리되며, 유저들간의 소통 및 로직 처리에 집중할 수 있다. (물론 당연히 DB와 직접 연결하는 것도 방법인데, 어차피 시그널을 주고 받아야 하기에 웹 서버랑 통신하는 것이 더 적합한 상황이 많을 것이다)


마치며

다시금 소켓 서버 이야기로 넘어가자면, 소켓 서버의 단점에 대한 해결책이 액터 기반 모델 (Akka, Orleans같은)이 고려될 수 있다. 다만 적절한 단위로 Actor를 쪼개지 않으면 수행 시간이 길어질 수 있는 이슈, Actor를 너무 작게 쪼갤 경우 메시지 핑퐁으로 인한 로직 구현상의 어려움, 그 과정 자체도 어느정도 측면도 있다보니 조금 더 발전해야 된다고 생각한다.

그래서 여전히 게임 서버로는 전통적인 소켓 서버 모델을 개선해서 사용하고 있는 팀이 더 많은 것도 사실이다.

웹 서버는 게임이 아닌 분야에서 훨씬 더 많이 사용되고, 그 사용자가 많은 만큼 문제에 대한 솔루션이나, 대응 방법도 많이 연구되어있고, REST API 자체가 기능을 결합도가 적게 끊어내기 쉬운 규칙을 가지고 있기에 웹 서버를 병행해서 사용했을 때의 장점이 생긴다고 볼 수 있다.

현재까지의 발전 상황으로 보았을 때는, 이와 같은 기준으로 서버를 구성 검토하면, 생산성 향상에 도움이 될 것이다.