들어가기 전에
버즈덤 프로젝트는 F-lab의 코칭 시스템 이용하면서 일정이나 피드백 관리가 있으면 좋을 것 같다는 마음에서 시작하게 됐다. 메인 기능은 코치가 코칭 가능한 시간을 등록하는 것과 멘티가 코칭을 신청하는 것인데 기본 내용은 다음과 같다.
- 코치 한 명에 여러 명의 멘티가 매칭될 수 있다.
- 코치는 코칭 가능한 시간을 등록한다.
- 멘티들은 코치의 일정을 조회해 그중 하나를 선택해 코칭을 신청한다.
그중 내가 맡은 것은 멘티가 코칭을 신청하는 기능이었는데 코치 한 명에 여러 명의 멘티가 있다 보니 동시성 문제가 발생할 것이라고 보고 동시성 제어에 대한 경험을 해볼 수 있을 것이라고 생각했다. 그래서 처음에는 수강 신청처럼 오픈되는 시간을 정해두고 멘티가 동시에 선착순으로 코칭 일정을 선점하는 방식으로 기능 명세서를 작성하기도 했다. 하지만 포인트는 한 명의 코치에 여러 명의 멘티가 매칭된다는 사실이기 때문에 정해진 시간은 배제하고 가기로 했다.
🧐 초기의 DB 설계는 어땠을까
조금씩 변경된 부분이 있지만 전체적으로 이런 구조의 테이블이 나오게 됐다. 정규화/역정규화도 반복해가며 어떻게 해야 효율적일지를 많이 고민했던 것 같다.
메인 기능과 관련된 주요 테이블은 다음과 같다.
- coaching_schedule : 코치가 코칭 가능한 시간 정보가 담긴 테이블
- mentee_schedule : 멘티가 코칭을 신청하여 확정된 코칭 시간 정보가 담긴 테이블
- feedback : 코치와 멘티가 코칭이 끝난 후 서로 남긴 피드백 정보가 담긴 테이블
- cancel_request : 코치와 멘티가 코칭 취소 요청을 보낼 때의 정보가 담긴 테이블
🤔 어떻게 동시성 문제를 해결할 수 있을까?
현재 상황은 이렇다.
- 하나의 coaching_schedule_id에 취소된 요청이 여러 개 생길 수 있다.
- 취소 여부를 확인할 수 있는 cancel_yn 컬럼이 있다. 그러니까 cancel_yn 값이 true인 여러 개의 row가 생길 수 있는 것이다.
- 여러 멘티가 동시에 코칭 일정 신청을 할 때 동시성 문제가 생길 수 있다. (결과적으로 하나의 coaching_schedule_id에 cancel_yn이 false인 row가 하나 생성되어야한다.)
- 생성도, 취소 요청 시점도 모두 제한이 없다.
자바의 Synchronized 키워드를 활용하기에는 서버를 증설했을 때 문제가 발생할 것이라고 판단해서 DB Lock을 활용해야겠다고 생각했다. 그런데 현재 테이블 설계상으로는 coaching_schedule_id가 유일한 값이 아니고 원래 존재하는 row를 조회해서 하는 작업이 아니기 때문에 select for update 같은 쿼리도 활용할 수 없다. 그래서 어떻게 해야 이 문제를 해결할 수 있을까 고민했다.
내가 생각한 건 두 가지였다.
- 트랜잭션 시작 시에 코칭 일정 select문에서 부터 lock을 거는 방법
- DB 설계가 잘못된 거라면 변경하는 방법
그리고 멘토링 시간에 멘토님께 조언을 구했다. 작업이 이루어지는 해당 테이블이 아닌 다른 테이블에서부터 Lock을 거는 것은 좋지 않은 방법이어서 제외하고 다른 방법을 더 고민하기로 했다. 그리고 멘토님께서 정확한 기능 명세를 확인하기 위해서 신청 시점과 취소 시점에 대해 질문을 주셨는데 그 이후에 혼자 고민을 해보다가 기능 명세 자체를 변경해볼까하는 생각이 들었다.
신청 시점과 취소 시점을 제어하면 coaching_schedule_id 컬럼을 유일한 키로 가져갈 수 있다고 생각했기 때문이다. 동시에 접근해서 insert문을 실행해도 DuplicatedKeyException이 발생하니까 문제를 해결할 수 있지 않을까 생각했다.
🤨 과연 최선의 방법일까?
그런데 내가 놓친 문제들을 멘토님께서 알려주셨다.
우선 코칭 일정을 신청하고 취소 요청 여부까지 한 테이블에서 관리하다 보니 책임이 분리되지 않았고, 그러면서 유일키를 가지지 못하다 보니 동시성 제어를 하기 어려워진 것이다.
거기다 이 작업이 다른 직무의 분들과 협업하는 프로젝트라고 생각한다면 아예 기능 명세를 변경하는 것이 과연 최선의 방법일까를 고려해야 한다는 말씀도 해주셨다. 아예 스펙 자체를 변경해 버리는 것을 쉽게 생각하다 보면 그게 습관이 되어서 나중에는 협업 시 갈등을 불러올 수도 있고 어려움을 겪을 수 있으니 이런 작은 프로젝트에서도 조심해야 했는데 놓치고 있었다.
그래서 최종적으로 mentee_schedule 테이블의 책임을 분리하고 pk 값을 변경하기로 했다.
변경 사항은 다음과 같다.
- coaching_schedule
- 매칭 여부를 확인하고 매칭이 되지 않은 일정에 신청하기 위한 match_yn 컬럼이 추가되었다.
- mentee_schedule
- coaching_schedule_id를 pk로 설정하여 유일한 값으로 row가 생성되도록 했다. (unique key로 설정할 수도 있었지만 id 컬럼 자체가 무의미하기 때문에 변경)
- 기존의 cancel_yn 컬럼을 제거하여 취소 히스토리는 모두 cancel_request 테이블에서 확인할 수 있도록 했다.
이렇게 변경하고 나니 기능 명세를 변경하지 않고도 내가 걱정했던 문제를 모두 해결하면서 초기에 설계할 때의 문제점들을 개선할 수 있었다. 참고로 ERD도 멘토님 조언에 따라 왼쪽에서 오른쪽으로 뻗어져나가는 방식으로 변경했더니 훨씬 눈에 잘 들어오게 고 관계를 파악하기에 좋았다.
마무리
사실 처음부터 더 세세하게 생각해가며 설계를 했으면 이렇게까지 고민하지 않았어도 됐을 것 같은데 하나씩 놓친 것들이 쌓여서 상대적으로 긴 시간을 고민하는 데 쓰게 되었다. 미숙함에서 온 실수같아서 글을 작성하면 창피거리가 하나 더 늘어나는 게 아닐까 걱정했는데 멘토님께서 그런 걱정보다 처음에 왜 블로그를 운영하게 됐는지를 먼저 생각하라고 말씀해 주셔서 기록을 남기기로 했다. 애초에 내 실수와 성장을 모두 담으려고 했었고 누군가에게 한 줄의 피드백이라도 받게 된다면 그것 또한 내가 성장할 수 있는 기회라고 생각하기로 마음먹었었는데 남들에게 어떻게 보여질지를 더 고민했던 것 같다.
그래도 이번에 DB 설계를 하면서 정규화로 인한 Join 필요성 때문에 성능 문제를 걱정하며 역정규화도 했다가 다시 정규화하는 경험도 했고 기능 명세를 변경하는 것에 대한 생각이나 설계의 중요성을 배워서 좋은 경험을 했다고 생각한다. 다음 번에는 설계를 더 촘촘히 하고 기능 명세를 변경하는 것에 있어서도 쉽게 생각하지 않게 주의하면서 할 수 있을 것 같다.
'노트 > F-lab' 카테고리의 다른 글
가변 인자를 다루기 위한 Functional Interface 활용 (0) | 2023.07.01 |
---|---|
[Redis] 설치 및 간단한 테스트 해보기 (Ubuntu 18.04) (0) | 2023.06.28 |
Github Actions: CI/CD 구축 (feat. Docker) (0) | 2023.06.21 |
RabbitMQ를 활용한 메시지 처리 방식 구현: 설치부터 적용까지 (0) | 2023.06.17 |
Github Actions: CI/CD 구축 (feat.shell script) - 실전편 (2) | 2023.06.04 |