ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TIL] 풋살 온라인 프로젝트 종료 - 2부 (ERD 수정해보기)
    TIL 2024. 6. 8. 22:34

     

     > https://donkim0122.tistory.com/69 1부 보기

     

    학습 키워드: MySQL, DB, Normalization, anomaly

     

    Applying DB Normalization

    1) ERD:

    Figure 1. 문제의 ERD

     

     

    2) 문제점?:

     풋살 온라인 프로젝트에서 강화 기능을 구현할 때 inventory 테이블의 count 컬럼 때문에 강화 시 인벤토리 수정 시 쿼리가 불필요하게 많아진다고 느꼈다. 이 테이블은 player 테이블과 user 테이블 각각의 id를 FK로 가지고 count와 level을 기록하여 인벤토리 항목 (보유 선수)의 강화 상태와 보유 수량을 유지하도록 설계됐다. 문제는 이런 형태라면 강화가 성공했을 때 테이블의 여러 row를 수정해야한다는 것이다. 예를 들면, 'user1'이라는 유저가 레벨 1의 '메시' 선수 카드를 보유중일 때 이를 레벨 2로 강화하는데 성공했다면, 인벤토리에서 level1 카드의 count를 줄이고, 레벨 2 카드의 카운트를 늘려야한다.

     

     그런데 문제는 해당 레벨의 플레이어 카드를 유저가 보유중인지 알 수가 없어서 upsert를 사용해줘야 하는데, level이 non-key attribute에 해당하기 때문에 결정자로 사용될 수 없어서 upsert를 사용할 수 없다는 점이다. 처음 설계할 때는 아이템 항목이 여러개 생성되는것보다 한 항목에 유지되는게 좋다고 생각했는데, 강화 레벨 정보가 같은 테이블에 있어서 오히려 이 때문에 한 항목만 수정할 걸 여러 항목에 걸쳐 수정하게 되어 효율이 떨어지게 된 것이다.

     

     

    3) inventory 테이블 원자화 하기?:

     그래서 생각했던 방법이 count column을 없애고 모든 inventory를 원자화 하는 것이다. 이렇게 되면 중복 선수 데이터가 엄청 많이 생기게 되는데, 오히려 강화 성공 시 한 개 row의 level 정보만 patch로 수정해주면 되기 때문에 강화 쿼리가 매우 간결해진다. 팀원들하고도 얘기를 나눴던 내용이었으나 결국 맡으실 분이 없었고 혼자서 하기엔 해당 주차에 여러 이유로 시간이 부족했어서 동작하는 상태로만 두고 개선하지 못한 것이 아쉬운 부분이다.

     


    4) MatchQueue 테이블 없애기?:

     매치 메이킹 시 팀이 준비된 유저만 추려내기 위한 방법을 고민했을 때 떠오른 방법이었는데, 이번 프로젝트의 피드백 중 하나가 바로 이 MatchQueue 테이블이 필요 없다는 것 이었다.

     

    // src/routes/games.route.js
    
    ...
    
    const matchMaking = async (myUserId) => {
      const me = await ec.userChecker(myUserId);
      const opponent = await userPrisma.$queryRaw`
        SELECT *
        FROM MatchQueue mq
        INNER JOIN User u
        ON mq.user_id=u.user_id
        ORDER BY ABS(u.rating - ${me.rating})
        LIMIT 10
      `;
      if (opponent.length < 2) throw new Error("상대를 찾을 수 없습니다.");
      const index = Math.trunc(Math.random() * (opponent.length - 1)) + 1;
      return opponent[index];
    };
    
    ...

     

     처음 이 기능을 구현할 때는 팀 인원이 3명이 맞는지 확인하기 위한 방법이 team 테이블에서 동일한 user_id를 가진 항목이 3개 인지 확인하는 것 밖에 없다고 생각했었다. 그래서 어차피 테이블을 JOIN해야 한다면 팀을 추가하는 시점에서 유저의 준비 상태를 확인하여 유저의 ID 하나만 기억하는 방식으로 쿼리를 단순화하면 된다고 생각했다. 피드백 이후 좀 더 고민해보니, user 테이블에 준비 상태를 나타내는 column을 추가하면, 팀 추가나 제거 시 수정 쿼리 횟수는 동일하지만 매치 메이킹 작업에서 JOIN이 필요 없게된다.

     


    5) 그 외 사항:

     user 테이블의 승 무 패 기록이 필요 없다는 피드백 (다른 팀의 피드백이지만 우리 팀에도 적용된다)을 들었는데, 아마 MatchHistroy 테이블을 쿼리하여 이를 추론할 수 있고, 매치 기록의 수정이나 삭제가 일어나면 승무패 기록과 차이가 생기는 anomaly 현상이 발생하기 때문인 것 같다. 그러면 같은 이유로 위의 4) 번 또한 컬럼의 추가 없이 처음의 생각대로 팀 테이블만 확인 하는 것이 옳다는 뜻인지도 모르겠다.

     


    6) 결론:

     DB 지식 부족으로 많이 무너지게된 프로젝트였다. 대충 설계한 DB로 인해 코드 수정도 잦고 DB 수정도 잦다보니 전체적으로 시간도 부족해지고, 불필요한 쿼리 호출 코드도 많아서 성능이 저하되는 등 여러 군데에서 허점이 생겼다. 이번 프로젝트를 계기로 DB 설계의 중요성을 깨달았으며 다음 개인 과제부터는 여유 시간을 두고 설계에 좀 더 집중하여 DB를 추가로 수정할 일이 없도록 해야겠다.

     

     

    --

     

    REFERENCES:

     

     

    GitHub - donkim1212/futsal-online

    Contribute to donkim1212/futsal-online development by creating an account on GitHub.

    github.com

     > 프로젝트 repo

     

    [TIL] DB 정규화 (Database Normalization) - 1

    > 이번 풋살 온라인 프로젝트를 진행하면서 어려움을 겪었던 DB 설계에 대한 부분을 보완하기 위해 DB의 정규화에 관한 내용을 공부해보았다. 학습 키워드: Database, normalization, normal form, 1NF, 2NF Da

    donkim0122.tistory.com

     > 이전 TIL

    728x90
Designed by Tistory.