안녕하세요, 저는 데브시스터즈에서 Frontend R&D Engineer로 근무중인 문태근 이라고 합니다. 게임 개발과 운영에 필요한 다양한 백오피스 개발 및 기술 연구를 담당하고 있습니다.
이 글에서는 게임 개발에 필요한 웹앱을 개발했던 경험을 공유하고, 그 과정에서 가장 중요하게 생각했던 가치관인 “단순한 설계”에 대해 이야기해 보고자 합니다.
#문제 상황
게임 클라이언트를 만드는 데는 소스 코드 외에도 게임에 필요한 사운드나 이미지 같은 리소스들이 필요합니다. 일반적으로 리소스 제작팀은 Dropbox, Google Drive 등의 웹 드라이브를 통해서 클라이언트팀에게 리소스를 전달합니다.
하지만 웹 드라이브를 통한 전달에는 단점이 있습니다. 바로 이미지_최종_진짜최종_final(2).png과 같이 버전 컨트롤이 어렵다는 것입니다. 특히 게임 개발처럼 업데이트가 빌드 단위로 진행되는 프로젝트의 경우, 파일 개별의 버전이 아니라 전체 파일 단위에 대한 버전 컨트롤이 필요합니다. (특정 시점으로 되돌려야 하는 경우 있음)
그렇다면 왜 처음부터 Git과 같은 VCS를 바로 쓰지 않고 웹 드라이브를 통해 전달하는 것일까요? 여러 가지 이유가 있지만, 가장 큰 이유는 프로그래머가 아닌 팀원들에게 Git의 진입장벽이 높기 때문입니다.
Sourcetree나 Fork 같은 GUI 클라이언트를 쓰면 어느 정도 해결할 수 있으나, 여전히 아래와 같은 문제점이 있습니다.
- Git을 써야 한다는 심리적 부담감
- Merge Conflict 해결의 어려움
- 비용 발생 (GUI Client 라이센스, Github 계정 → 사용자 수에 비례)
이렇듯 프로그래머가 아닌 분들이 Git을 사용하기 위해서는 많은 심리적, 물질적 부담이 불가피해 보입니다.
그래서 쿠키런: 킹덤 개발팀은 버전 컨트롤이 가능한 사운드 파일 전달 Web App(a.k.a Sonic) 을 직접 만들기로 결정했습니다.
Git을 써야 한다는 심리적 부담감→ 사용자에게는 최소한의 필요한 interface만 노출 (push, checkout)Merge Conflict 해결 어려움→ 무조건 덮어쓰도록 설정비용 발생→ 최소한의 서비스 호스팅 비용만 발생 (사용자 수 비례 X)
#0. 설계 원칙
전 마이크로소프트 CTO인 Ray Ozzie는 2005년 내부 문건에서 다음과 같이 말했습니다.
복잡성은 살인적입니다. (Complexity Kills)
복잡성은 사용자, 개발자, IT 부서의 생명을 앗아갑니다. 복잡성은 제품을 계획, 구축, 테스트 및 사용하기 어렵게 만듭니다. 복잡성은 보안 문제를 초래합니다. 복잡성은 관리자의 좌절을 초래합니다.
실제로 저도 불필요하게 복잡하게 작성된 코드와 시스템으로 인한 문제를 느꼈던 적이 굉장히 많았습니다. 그래서 Sonic을 제작할 때는 UI와 Architecture 두 가지 측면에서 복잡성을 최소화하기 위해 노력했습니다.
- User Interface : 사용자에게 노출되는 인터페이스 최소화
- Architecture : 코드 및 컴포넌트 최소화
이제 각각의 관점에서 어떻게 서비스를 구현했는지 알아보겠습니다.
#1. User Interface
Sonic의 사용자는 사운드팀과 클라이언트팀 두 가지 그룹으로 나뉘어 집니다. 요구사항을 정리한 결과, 사운드팀은 전달 (push), 클라이언트팀은 동기화 (checkout) 인터페이스만 있으면 충분하다는 결론을 내렸습니다.
그 외에 잘 사용하지 않으면서 서비스의 복잡도만 증가시키는 기능들은 모두 제외하기로 하였는데, 자세한 내용은 뒤쪽(2. 스펙 줄이기)에서 다루어 보겠습니다.
💡 용어 정의
Bundle: 모든 사운드 파일의 묶음Revision: 번들의 버전 (Git의 commit에 대응). 사운드팀이 내부적으로 관리push: 특정 revision을 서버에 업로드하는 행위 (by 사운드팀)checkout: 특정 revision으로 리소스 파일을 동기화 하는 행위 (by 클라이언트팀)
#2. System Architecture
서비스는 Next.js를 사용한 풀스택 웹앱으로 docker image를 통해 배포하였습니다. API로는 tRPC를 사용했는데, 백엔드에 엔드포인트를 추가하면 프론트엔드에 이에 대응되는 함수를 자동 생성해주어 API 개발 공수를 크 게 줄일 수 있었습니다. 풀스택 앱 개발을 할 일이 있다면 한번 사용해 보시길 추천합니다.
#2.1. 핵심 기능
우리의 목적은 사운드팀이 업로드한 여러 파일중 (1) 변경된 파일만 추려내어 (2) 시스템에 기록하고, (3) 이 파일들만 클라이언트팀에서 다운로드하는 것입니다 (변경사항이 있는 파일만 전달하는게 중요한 이유는, 클라이언트팀에서는 전달받은 파일에 암호화 등 추가 가공을 하기 때문에 변경사항이 없는 파일까지 불필요하게 전달되면 오버헤드가 크게 늘어나기 때문입니다).
위 3가지 핵심 기능을 어떻게 구현했는지 사용 플로우와 함께 전체 흐름을 알아보겠습니다.
#2.1.1. 변경된 파일 추려내기 (Diff Files)
xxHash32의 WASM 빌드를 사용해 이전에 업로드 된 파일의 해시값과 첨부된 파일의 해시값을 비교했습니다. SHA-256은 전체 파일 업로드시 약 1분, xxHash32는 약 3초 정도가 걸려서 최종적으로 xxHash32를 선택했습니다.
#2.1.2. 번들 데이터 시스템에 기록하기 (Persistence)
번들 데이터는 크게 두가지로 구성됩니다.
- 사운드 파일들
- 번들 메타데이터
- 제목
- 세부 설명
- Revision
- 작성자
- 생성 시간
- 파일명 및 VersionId
사운드 파일들은 AWS S3의 Versioning Bucket 에 저장하였습니다. 메타데이터와 별도로 저장한 이유는 사운드 리소스는 바이너리 파일이기 때문에, 텍스트 형태인 메타데이터와 함께 저장하기 어렵기 때문입니다.
- 일반 Bucket : 이름만으로 파일 식별
- Versioning Bucket : Versioning 옵션이 켜진 버킷에서 파일들은 이름 외에 VersionId라는 추가 식별자를 부여받습니다. 다운로드시 이름만 제공하면 최신 버전을, 특정 versionId까지 명시하면 해당 버전의 파일 다운
그리고 파일 목록 (이름, versionId)을 메타데이터에 넣기로 하였습니다.
이를 통해 메타데이터만 있으면 해당 번들에 어떤 파일이 있는지와 이들의 S3 versionId를 알 수 있었습니다.
메타데이터의 경우, DBMS를 사용해 직접 저장하지 않고 local git을 DB처럼 사용하기로 했습니다.
- git에 저장되는 데이터 schema가 Sonic 요구사항에 충분함 → schema 관리 필요 X
```bash
$ echo $CONTENTS > sound_file_meta.csv
$ git add sound_file_meta.csv
$ git commit
--author $YOUR_EMAIL
-am $TITLE
-m $DESCRIPTION
$ git tag -a $REVISION -m $REVISION ``` - 제목, 세부 설명 → Git commit message (multi line) - Revision → Git tag -
