Post

메이플 주문서 시뮬레이터 개발기

메이플 주문서 시뮬레이터 서비스의 기획과정과 프로젝트의 구조, 기본적인 기능을 설명합니다.

12월 초, 메이플스토리 월드 라는 넥슨의 플랫폼에서 옛날 메이플을 재현해 서비스하는 메이플랜드라는 게임이 오픈했다. 옛날의 메이플을 좋아했던 사람들이 몰려 금방 인기순위 1위에 올랐고, 나도 내가 직접플레이 하지는 않았지만 다른 사람이 플레이하는 것을 재미있게 보고있었다. 메이플스토리하면 빼놓을 수 없는 컨텐츠가 강화다. 주문서라는 아이템을 이용해서 본인의 아이템을 강화시키는 시스템인데, 약간의 도박성 컨텐츠이기 때문에 유튜브에서도 주로 메인컨텐츠로 사용된다.

강화 시뮬레이터 서비스가 없는 것은 아니였지만 플래시를 이용해서 한 가지 아이템만 강화할 수 있거나, 강화 과정을 사용자가 직접 참여하지 않고 계산된 결과만 보여주거나, 텍스트로만 과정을 진행해서 직접 강화하는 것 같은 느낌을 주지 않는 여러가지 단점이 있었다. 실감이 나지 않으니 흔히 말하는 액땜을 할 수가 없었다. 필요없는 아이템을 몇개 강화하며 실패한 아이템이 많아졌을 때 액땜을 했다고 진짜 하고싶은 강화를 시도하는데 그런 용도로도 사용하기 어려워보였다.

그래서 초기 기획은 다음과 같았다.

  1. 실제 인게임 내의 아이템 창처럼 아이템을 보여주면 좋겠다.
  2. 사용자가 직접 강화가 진행되어 가는 과정에 참여하면 좋겠다.

이번 포스팅에서는 대략적으로 어떤 방식으로 진행되었는지에 대해서 주로 작성한다. 기능구현의 세부사항은 다른 포스팅에서 다루어 보겠다.

초기버전

프로젝트를 처음 시작할 때는 여러개의 아이템에 대해서 시뮬레이션을 제공할 것이라고 생각하지 않았다. 그래서 스프링 부트를 기반으로 타임리프를 사용해서 특정 아이템에 대한 정적인 HTML, JS를 전달하기로 하였다.

아이템을 보여주는 부분, 주문서를 적용하는 컨트롤러 부분은 전부 HTML, CSS, JS를 직접 작성하였다. 그렇게 만들어진 초기버전에서는 세가지 아이템만 제공했다.

대략적인 구조는 다음과 같다.

피드백

메이플 아이템을 강화하는데는 아이템과 주문서가 필요하고, 아이템과 주문서를 얻기 위해서는 메소라는 돈이 필요했다. 그래서 실제로 강화를 하면서 들어가는 돈을 계산해주면 좋겠다는 피드백이 있었다. 그래서 아이템과 주문서의 가격정보를 직접 입력하고 강화를 진행하면서 지금까지 사용한 메소정보를 보여주도록 업데이트 했다. 동시에 지금까지 구매한 아이템의 갯수, 그리고 성공한 주문서와 지금까지 사용한 주문서의 갯수도 보여주도록 업데이트 했다.

문제점

하지만 지금까지 구현한 방식은 사용자에게 제공할 아이템의 갯수가 소수로 제한되어 있다는 것을 가정하고 만들었다. 그래서 특정 아이템을 위한 HTML, 특정 아이템을 위한 JS를 작성해서 사용자에게 제공하고 있기 때문에 새로운 아이템의 강화 시뮬레이션을 제공하고 싶으면 새로운 HTML, 새로운 JS를 작성해야한다는 문제점이 있었다.

소수의 아이템만으로 사용자를 만족시킬 수 있다면 문제가 없었겠지만 내 예상 밖으로 더 많은 아이템에 대한 수요가 발생했다. 처음에는 수요가 있는 아이템에 대해 직접 HTML, JS를 작성해서 제공했다. 하지만 점점 감당할 수 없을 정도로 수요가 증가했고 아이템을 등록하는 폼을 만들고 아이템정보를 DB에 저장한다면 더 유연하게 대응할 수 있을 것이라 생각했다.

두번째 버전

초기버전에서는 동적인 기능 없이 특정 페이지에 들어가면 정적인 페이지만을 제공하였다. 하지만 두번째 버전에서는 아이템을 쉽게 추가하는 것은 물론이고, 아이템의 갯수가 많아졌으니 사용자가 아이템을 검색하는 기능. 그리고 가장많이 플레이된 아이템의 순위정보를 나타내는 동적인 정보도 함께 제공하고 싶었다.

이전 방식은 정적인 페이지만을 제공했기 때문의 화면의 일부분만 변경해도 전체 HTML이 모두 새로고침되어 화면이 깜박이는 현상이 발생했다. 이를 방지하기 위해서는 React프레임워크를 사용하기로 결정했다.

그리고 아이템 정보를 DB에 저장해두는 것이 아니라 특정 아이템을 위한 HTML을 작성해서 그 안에 아이템에 대한 저보를 하드코딩으로 작성했다. 두번째 버전에서는 아이템 정보는 DB에 저장해두고 페이지가 렌더링 될 때 서버에 요청을 보내서 아이템 정보를 불러오는 방식으로 구현했다.

이전에는 백엔드 서버의 존재의미가 없었다 단순히 정적 데이터만을 사용자에게 보여주는 역할만 했기 때문이다. 이번에는 백엔드 서버에서 비즈니스 로직도 수행해보고 DB서버에서 데이터도 조회해서 사용자에게 응답하는 등 존재의미가 있는 서버로 만들어보고 싶었다.

프로젝트 구조

인프라

초기에는 AWS EC2, AWS RDS를 사용했다. 하지만 생각보다 사용자가 몰리면서 프리티어에서 주어지는 자원을 금방 소모해버렸고, 계정마다 300$의 크레딧을 제공해주는 Google Cloud VM 인스턴스, Google Cloud SQL로 이전했다.

서버

  • Google Cloud VM 인스턴스 e2-small
    • Intel Broadwell 2코어 2.25Ghz
    • 메모리 2GB
  • Ubuntu 20.04 LTS

데이터베이스

  • Google Cloud SQL
  • MySQL 8.0
  • vCPU 2개
  • 메모리 8GB
  • SSD 저장용량 10GB

사용자의 입력에 의해서 다양한 데이터가 들어가는 것이 아니라 이미 형식이 정해져있는 아이템 정보가 주로 저장되고, 미래에 아이템별로 댓글기능을 추가할 것으로 예상해 테이블별로 관계를 표현하고자 관계형 데이터베이스를 선택했다. 그중에서도 MySQL은

NoSQL을 선택하지 않고 RDBMS를 선택한 이유는 다음과 같다.

  • 폭발적인 데이터를 저장할 일이 없다.
  • 데이터 기반 서비스를 제공할 일이 없다.
  • 아이템 테이블과 댓글 테이블의 관계를 설정하고 싶어서
  • 아이템 정보, 멤버 정보, 댓글 정보 등 서비스상의 모든데이터가 정형화 되어있다.

검색 기능

검색은

  • 아이템의 이름
  • 착용 가능 직업
  • 최소 레벨
  • 아이템 카테고리 네 가지 조건으로 아이템을 검색할 수 있는 기능이다.

아이템 검색쿼리에 어떻게 인덱스를 잘 먹일 수 있을지 고민해보았다. 일단 아이템의 이름 컬럼으로 인덱스를 먹히는 것은 안된다. 이름으로 검색을 한다고 하면 like %(검색어)% 로 조건을 명시해야한다. 보통 검색이라고 하면 내가 검색한 문자열이 결과 문자열에 포함되어 있는것을 의미한다. 문자열 매칭에 앞부분이 일치하지 않으면 인덱스를 먹일 수 없기 때문에 아이템의 이름 검색으로 인덱스를 먹이는 것은 불가능하다.

최소 가능 직업, 최소 레벨, 아이템 카테고리 이 세가지로는 인덱스 검색 범위를 줄이는 것이 가능하다. 그래서 직업, 레벨, 카테고리 이 세가지의 컬럼으로 인덱스를 만들어 검색속도를 증가시킬 수 있다고 생각한다.

아이템 목록 페이지네이션

개발초기에는 페이지에 진입하면 존재하는 모든 아이템 정보를 사용자에게 전달했다. 하지만 사용자가 모든 아이템정보를 보지 않을 뿐더러, 단순 조회요청에 모든 데이터를 보내는 것은 리소스 낭비라고 생각되었다. 그래서 처음 몇개의 아이템만 클라이언트에게 전달하고 사용자가 더 보고싶은 결과는 더보기 버튼을 이용해서 제공하였다.

현재 더보기 기능은 limit, offset 쿼리를 이용해서 구현되어있는데 No Offset 기능을 사용해서 리팩토링 해볼 계획이다.

아이템 랭킹

아이템 랭킹 기능도 마찬가지로 페이지네이션으로 구현되어있다. 조회수(view_count)가 많은 순서대로 나열되고 서버로부터 순위(rank) 아이템의 아이디(item_id)와 이름(name)정보를 가져와 사용자에게 보여줍니다.

This post is licensed under CC BY 4.0 by the author.