한결과 레지아이스

[42GG] DB설계 수정기 본문

42GG/설계

[42GG] DB설계 수정기

miniwho 2022. 8. 2. 02:00

기존의 42GG DB 설계를 박정규 멘토님께 조언을 구해 수정했습니다.

원래는 어땠고, 우리끼리 어떻게 고민했으며, 결국 어떻게 바꿀 지에 대해 적었습니다.

 

0. DB 설계도 변천사

 

a. 기존 설계

b. 멘토링 이후 기존 설계에 관계성 추가

c. 매핑 테이블을 이용해 수정한 결과

 

42GG 서비스는 처음에 어떤 종류의 게임이든, 팀이 몇 개 존재하든, 각 팀에 몇 유저가 존재하든 상관없이 대전 서비스를 제공할 수 있도록 확장성을 염두에 두고 설계를 했다.

이 과정에서 어려움을 겪어 기존의 형태로 설계했는데, 먼저 어떤 시행착오를 겪었는지 설명해보려고 한다.

 

1. 태초의 설계

 

DB설계를 처음 해봤고, 팀원들도 함께 미숙하던 시절이라 최대한 확장성을 염두에 두고 만드려면 어떻게 해야 할까 고민을 많이 했다. 우리는 최대 2명이 한 팀이고 두 팀이 게임을 하는 탁구 서비스만 제공하는데 그치고 싶지 않았다. 우리의 DB 설계가 불특정 다수의 구성원으로 구성된 불특정 다수의 팀을 담을 수 있게 만들고 싶었다.

 

그래서 생각한게 다음과 같은 설계였다.

 

Slot은 Group을 갖는다. Group은 pk값인 아이디와 별개로 UUID로 이루어진 group_id를 갖는다. 이렇게 한 이유는 같은 UUID를 가진 다수의 데이터를 만들고 싶었기 때문이다. 그리고 이 그룹은 team_no를 갖는다.

Slot이 “abcd”라는 group_id를 가졌다고 생각해보자(UUID의 형태는 아니지만 이해해주세요). 그러면 우리는 이 Slot에 속한 팀들을 찾기 위해 Group 테이블을 찾는다. Group 테이블에서 “abcd”라는 group_id를 가진 데이터들을 모두 찾아오면, 몇 개의 team_no들이 나올 것이다. 예컨대 team_no 123과 124가 “abcd”에 속해있음을 알 수 있을 것이다.

Team도 마찬가지이다. pk값인 아이디와 별개로 team_no를 갖는다. 그래서 team_no가 123인 데이터가 하나 또는 여러개 존재할 것이다. Team 테이블에서 team_no가 123인 데이터를 모두 찾아오면, 어떤 유저들이 이 팀에 속해있는지 알 수 있다.

 

2. 현재의 설계로 바뀐 과정

 

이런 설계를 개발 과정에서 한 번 다 갈아 엎고 다시 시작했는데, 아쉽게도 왜 그런 결정을 했었는지 기록이 남지 않았고 기억도 잘 나지 않는다. 어렴풋이 떠올리자면 위에서 떠올린 구조대로 개발하는 과정에서 어려움을 겪었고, 비효율적이라고 생각했던 것 같다.

그래서 우리는 확장성을 일부 포기하고, 한 팀의 유저는 최대 두 명으로, 한 게임의 팀은 둘로 고정시켰다. 이렇게만 해도 충분히 많은 종류의 대전 게임/스포츠를 포용할 수 있을 것이라고 생각했다.

 

 그래서 이와 같이 설계가 바뀌었다.

먼저 중간의 Group이 사라졌다. Team은 두 개의 user_id를 가질 수 있다. 슬롯은 두 개의 team_id를 가진다.

DB가 굉장히 단순하게 바뀌었고, 우리는 몹시 편해진 것만 같았다.

하지만 응답 데이터를 뷰에 맞추면서 우리는 엄청난 고난에 빠지게 되었는데…

유저의 편의상 팀을 단순히 team1과 team2가 아니라 myTeam과 enemyTeam으로 나누어야 할 일이 생겼기 때문이다.

우리는 현재의 유저가 team1과 team2 중 어디에 속했는지를 찾고, 둘 중 하나를 myTeam으로 하나를 enemyTeam으로 지정해준다.

그 과정에서 이런 코드가 나온다. (데이터의 클래스 같은 정보들은 이해에 혼란만 더할 것 같아 적당히 생략했습니다)

void putUsersDataInTeams(team1, team2, user, myTeams, enemyTeams) {
        teamPos = getTeamPosFromUser(user, team1, team2);

        myTeam = teamPos.getMyTeam();
        enemyTeam = teamPos.getEnemyTeam();
        myTeamUser1 = myTeam.getUser1();
        myTeamUser2 = myTeam.getUser2();
        enemyTeamUser1 = enemyTeam.getUser1();
        enemyTeamUser2 = enemyTeam.getUser2();
        if (myTeamUser1 != null) {
            myTeams.add(myTeamUser1에 대한 정보);
        }
        if (myTeamUser2 != null) {
            myTeams.add(myTeamUser2에 대한 정보);
        }
        if (enemyTeamUser1 != null) {
            enemyTeams.add(enemyTeamUser1에 대한 정보);
        }
        if (enemyTeamUser2 != null) {
            enemyTeams.add(enemyTeamUser2에 대한 정보);
        }
    }

무시무시하다. 이런 일은 하나의 슬롯에 속한 유저들 모두에 무언가 하려 할 때마다 반복된다.

addEventUser(game.getTeam1().getUser1(), "랜덤 당첨 이벤트");
addEventUser(game.getTeam1().getUser2(), "랜덤 당첨 이벤트");
addEventUser(game.getTeam2().getUser1(), "랜덤 당첨 이벤트");
addEventUser(game.getTeam2().getUser2(), "랜덤 당첨 이벤트");

정말 슬프게도 같은 메서드를 이런식으로 4번 호출하게 된다. 반복문을 돌리려고 해 봐야 고작 2개를 반복하기 위해 리스트에 넣어주고 하는 일이 더 번거롭고 보기 안 좋은 것 같아 이런 식으로 처리하게 되었다.

애초에 db에서 가져올 때 리스트로 가져왔더라면..! 뭔가 태초의 DB설계가 어렴풋이 떠오르다 말다 하곤 했다.

그러다 멘토링을 받고 더 개선된 방안으로 향해갈 수 있었는데..

 

3. 매핑 테이블을 적용한 DB설계

 

살펴볼 부분만 조금 추려내 보았다. 무언가 1번에서의 설계와 같은 느낌도 든다! 사실 거의 비슷하다는 생각도 든다.

매핑 테이블에 대해 먼저 설명하자면, 여러 테이블(여기선 둘)의 PK를 외래키로 참조하는 테이블이다. 슬롯팀과 팀유저가 매핑 테이블이다.

이 테이블들의 역할이 뭐냐 하면, 슬롯과 팀을 엮어주고 팀과 유저를 엮어주는 역할을 한다. 슬롯 테이블은 이전처럼 팀 테이블의 아이디를 들고 있지 않다. 하지만 나는 그 슬롯에 어떤 팀이 있는지 알 수 있다. 어떻게? 슬롯팀이라는 매핑 테이블에서 해당 슬롯의 pk인 id로 조회를 하면, 팀이 주루룩 나올 것이다.

 

팀에 속한 유저를 찾는 일도 똑같다. 팀은 예전과 다르게 유저 아이디를 들고있지 않다. 하지만 나는 팀에 속한 유저들을 찾을 수 있다. 어떻게? 팀유저 테이블에서 찾고자 하는 팀의 id로 조회를 하면, 유저 아이디가 나올 것이다.

1번의 설계에선 조금 달랐다. 이런 개념을 몰라서, 슬롯과 팀이 불필요하게 그룹 아이디를 갖고 있었다. 그때를 생각하면 ‘아, 거의 다 왔었는데’ 하는 아쉬움도 남는다. 그래도, 돌고 돌아오긴 했지만 좋은 설계에 더 가까워졌다는 생각이 든다.

 

매핑 테이블을 적용하며 얻게 된 것은 다음과 같다.

  1. 슬롯(팀) 엔티티 조회 시 유저(팀) 목록을 리스트로 받아오게 될 것이고, 이 덕에 메인 서비스 로직 코드가 좀 더 깔끔해질 것이라고 예상한다.
  2. 기존에 목표했던 확장성을 얻게 되었다. 이제 우리는 같은 구조로 불특정 다수의 유저가 속한 불특정 다수의 팀을 가진 게임/스포츠도 다룰 수 있게 되었다.

엔티티를 수정하고 코드의 로직을 변경하는 것은 쉽지 않지만 매우 보람찰 것 같다. 코드를 다시 짜게 되면 위의 난삽한 코드가 어떻게 변했는지 다시 한 번 포스팅 해보려 한다. 업데이트에 앞서, 테이블을 변경할 때 기존 db의 데이터들을 새로 바뀐 테이블에 어떻게 가져올지 그 방법에 대해 더 알아봐야 한다. 하지만 우리는 또다시 해낼 것이다. 언제나 그랬던 것처럼. 

 

끝은 아니다

사실 멘토님께서 말씀해주신 것은 더 있다. 사용자 정보, 게임 정보, 슬롯 정보 등의 메인 엔티티들에는 최초 등록, 마지막 등록, 최초 수정, 마지막 수정 이 네가지를 꼭 넣어주는게 좋다고 말씀해 주셔서 이 부분도 추가해야 한다.

그리고 현재 우리는 현재매치정보라는 테이블의 데이터를 생성했다 삭제했다 하는데, 삭제는 정말 안좋은 것이라고 하셨다. flag를 하나 만들어서 관리하거나 이력 테이블을 하나 만드는 것이 좋다고 하셔서, 향후 이 부분도 발전시켜야 한다.

'42GG > 설계' 카테고리의 다른 글

[42GG] 시퀀스 다이어그램 2부 - 로직을 중심으로  (2) 2022.08.12
Comments