(프로젝트 회고) 파티매칭의 구현

김인회·2022년 3월 8일
0

OTT프로젝트

목록 보기
1/6

📖 개요

한달동안 진행했던 프로젝트(https://github.com/inhoekim/FinalPrj)에서 내가 전적으로 담당해서 맡은 파트는 사이트의 전체적인 프론트 부분과 자동매칭 시스템의 설계였다. 그 중 자동매칭 시스템 설계에 대한 구현과정 내용을 기록으로 남겨보고 싶어 본 포스트를 남긴다.

🎡 자동매칭 시스템

먼저 프로젝트에서 필요했던 자동매칭의 기능의 요구사항에 대해서 한 번 간단히 서술해보자면,

  1. 사용자는 자신이 원하는 OTT를 골라서 파티장으로 파티를 창설하거나 혹은 파티원으로 파티에 가입할 수 있어야 한다.

  2. 매칭시스템에 등록된 파티장들은 등록된 순서대로 매칭의 우선권을 갖는다. (매칭 시스템에 먼저 등록된 파티부터 파티원을 추가 시켜주어야 한다)

  3. 2와 동일하게 매칭시스템 등록된 파티원들은 등록된 순서대로 매칭의 우선권을 갖게 된다.

프로젝트에서 요구되는 매칭시스템은 말그대로 매칭 큐에 대한 구현이라고 볼 수 있었다. 먼저 매칭에 등록된 사람부터 우선적으로 한명 한명씩 매칭을 처리해주는 그런 형태였다.

여기서 내가 했던 고민은 그러면 큐를 메모리에 올려놓고 매칭작업을 담당하는 매칭서버를 따로 만들어야하나? 였었다.

그런데 생각해보면 우리 사이트가 실시간 게임 매칭처럼 몇초, 몇분 간격으로 빈번하게 매칭을 맺어주는 곳도 아닌데 굳이 매칭작업을 위해 서버의 메모리를 상시 사용해가며 작업을 처리할 필요가 있을까? 라는 생각이 머리 속에 맴돌았다.

단순하긴 하지만 매칭에 대한 정보를 DB(디스크)에 확실히 저장해놓고, 그때그때 매칭에 대한 요청이 들어올 때마다 데이터를 꺼내와 해결해 주는 방법으로도 사실 충분해 보였다.

물론 이와 같은 방법이 구현이 훨씬 간단할 것 같다는 판단도.. 컸었다🤗

시간만 충분했다면 큐를 이용한 매칭 시스템 구현에도 과감히 도전해봤겠지만 프로젝트 발표까지 주어진 시간이 무한정이었던 것도 아니였고, 또 팀장으로서 전체적으로 다른 팀원들의 코드도 봐야한다던가 내 담당파트 개발 이외에도 이곳저곳에서 시간을 할애해야 했었기 때문에 우선은 간단하게 DB 저장을 이용한 매칭 시스템 구현으로 방향성을 잡기로 하였다😏

📂 Order by로 구현한 매칭 시스템

큐를 이용하지 않는 매칭 시스템의 구현 방식은 간단했다.

만약 사용자가 파티원으로서 매칭을 시작한다면 먼저 매칭 시스템에 등록되어있는 파티를 검색한다(가입가능한 파티가 있는지 파티테이블에서 검색)

이 때 파티는 생성일 칼럼 기준으로 Order by 하여 검색되기 때문에 파티 테이블에 먼저 등록된 파티들이 매칭에 대한 우선권을 갖게 된다.

만약 지금 당장 가입할 수 있는 적절한 파티가 존재하지 않을 경우 사용자는 매칭대기 테이블에 등록되게 된다.

해당 테이블에 등록된 사용자들은 나중에 새로운 파티가 생성될 때까지 대기하게 되는데, 새로운 파티가 생성될 경우 마찬가지로 Order by을 통해 가장 먼저 대기 테이블에 등록된 유저가 우선적으로 선택 되어지는 구조이다.

매칭관련 코드-Controller(Link)

매칭관련 코드-쿼리문(Link)

🔌 하고싶지만 못한 것

이렇게 우리 프로젝트에서 매칭시스템은 발표날까지 오로지 RDB만을 이용해서 구현되었다.

사실 프로젝트 기간을 돌아보면 정말 정말로 열심히 달렸던 것 같았는데, 막상 발표가 끝난 후 나에게 찾아온 감정은 후련함보다도 아쉬움이 먼저였다 😥

하고 싶었지만 결국은 해내지 못했던 것이 굉장히 많았다. 서버 배포도 못했고, 초대코드 구현, 파티관련 세부기능, 유저 프로필 세부기능, 충분한 디버깅 테스트 뭐 이것저것.. 있었다.

큐를 이용한 매칭구현 역시도 당연히 그 중 하나였다. 특히나 이 주제는 프로젝트의 완성도를 떠나서 개인적인 공부에도 많은 도움이 될 것 같은 주제였기 때문에 꼭 해보고 싶은 마음이 많이 컸다.

그래서 발표 이후에도 이 부분만큼은 개인적으로 추가 작업을 진행해보고자 하였다 😎

🎉 큐를 이용한 매칭 시스템 구현 (Redis 이용)

매칭시스템의 기존 구현방식은 파티테이블과 유저대기테이블의 유기적인 데이터 이동으로 이루어졌다.

파티테이블은 파티에 대한 세부사항과 변경이력을 기록하기 위해서라도 데이터를 체계적으로 보관하고 관리할 필요성이 있었지만, 유저대기테이블의 역할은 사실상 매칭을 위한 임시저장소 그 자체일 뿐이었다. 대기인원이 발생하면 유저대기 테이블에 Insert되고 대기인원이 빠져나가면 Delete되는 단순한 구조일뿐이였고 이러한 공정에 대한 처리를 RDB 시스템으로 해결하는것은 그렇게 적합해 보이지는 않았다.

그렇다면 이러한 유저대기테이블을 처리할 어떤 다른 방법이 있을까? 라는 고민속에서 가장 먼저 떠올랐던 생각은 바로 캐싱(임시저장) 데이터베이스로 유명한 Redis의 이용이었다. Redis에서 지원하는 List 컬렉션을 사용한다면 내가 원하는 매칭큐 시스템을 충분히 구현할 수 있을 것 같았다. 그리고 무엇보다 이전부터 Redis를 경험해보고 싶다는 마음이 있었기 때문에 곧바로 진행해보기로 했다.

Redis를 이용한 구현이라고 해도 생각보다 특별할 것은 없었다.

신규 대기 인원은 List의 right에 Push해주고, 대기인원이 빠져나갈때는 Left로 Pop시키는 식으로 진행하였다.

유저들이 신청한 OTT에 따라 대기방을 개별적으로 구성해야하므로 WatingRoom + OTT_id 형식으로 List의 Key값을 잡았다. Value값의(List의 요소) 형태는 그냥 UserID로 잡았다.

또한 유저가 현재 대기방에 등록되어있는지 따로 확인해주기 위해 WatingRoom List에 등록될때 동시에 Strings 컬렉션에 Key는 유저의 아이디, value는 신청한 OTT_id로 같이 등록해주시는 식으로 구현하였다.

이후 고민했던 부분은 Redis와 Oracle DB의 트랜잭션 연동이였는데, Redis의 경우 분산트랜잭션을 지원하지 않았으므로 단순하게 @Transactional로 묶여있는 Oracle DB Service 작업안에 Redis DB 공정을 집어넣었고, 대신 Redis의 작업이 가장 마지막 공정에서 이루어지도록 배치하였다. 이와같은 방식이라면 Oracle의 작업이 정상적으로 종료된 이후에나 Redis 작업을 진행할 것이니 Redis의 Rollback(Discard) 문제는 신경쓸 것이 없었고, 만약 Redis의 작업처리중 오류가 발생할 경우 Oracle DB는 자연스럽게 Rollback 될것이라고 판단하였다.

Redis관련 코드-Service(Link)

Redis관련 코드-트랜잭션 처리(Link)

📏 매칭 이후의 변동사항

최초로 매칭을 맺어주는 작업 이외에 세부적으로 신경써줘야할 부분은 매칭 이후 변동사항이 존재하는 경우였다.

만약 매칭이 진행되고 나서 갑작스럽게 파티가 해체되는 경우, 한번 매칭이 완료된 유저들을 다시 다른 파티로 매칭해주어야 한다.

바로 가입할 수 있는 적절한 다른 파티가 존재한다면 해당 파티로 유저를 옮겨주면 그만이었지만, 그렇지 않은 경우 다시 Redis의 유저대기 리스트에 유저를 등록시켜주어야 했다.

즉 매칭을 맺어주던 과정이 밑과 같았다면,

Redis의 유저대기 리스트(매칭큐) LeftPop() -> Oracle의 파티매칭 테이블 Row에 Insert

이것을 역으로 돌려서,

Oracle의 파티매칭 테이블의 Row에서 Delete(혹은 상태값 변경) -> Redis의 유저대기 리스트(매칭큐) LeftPush()

위와 같은 방식으로 작업을 진행시켰다.

profile
안녕하세요. 잘부탁드립니다.

0개의 댓글