이번 챕터에서는 콘서트 예약 서비스의 주요 기능을 구현했습니다. 요구사항 분석을 바탕으로 플로우차트, 시퀀스 다이어그램, 클래스 다이어그램, ERD, API 명세서를 작성하며 설계를 진행했습니다. 이후 본격적인 기능 구현에 착수했습니다.
설계 과정에서 많은 어려움을 겪었습니다. 특히 플로우차트, 시퀀스 다이어그램, 클래스 다이어그램, ERD 작성이 익숙하지 않아 시간이 많이 소요되었습니다. 다양한 툴을 고려했지만, 각각의 러닝 커브가 존재해 이를 최소화하고자 Mermaid를 선택했습니다. Mermaid는 GitHub에서 지원되는 Markdown 기반 다이어그램 도구로, 처음 사용하는 데 익숙해지기까지 시간이 걸렸습니다.
설계를 기반으로 구현을 진행하다 보니 예상보다 설계가 부족하다는 것을 깨달았고, 구현 -> 설계 수정을 반복하게 되었습니다. 특히 클래스 다이어그램은 완전히 마무리하지 못한 채 진행되어 아쉬움이 남습니다.
콘서트 예약 서비스에서는 대기열, 콘서트, 유저, 결제와 같은 주요 기능을 구현했습니다. 이 중에서 대기열과 콘서트 기능에 대해 언급하겠습니다.
대기열은 콘서트 예약 시 트래픽 폭주를 대비해 서버 부하를 조절하는 기능입니다. 처음에는 RDBS를 사용해 구현했으며, 추후 Redis로 변경할 예정입니다. 대기열은 콘서트별로 생성되며, 요청 전에 대기열 토큰을 발급받아야 합니다. 토큰이 활성화되면 콘서트 관련 요청을 진행할 수 있습니다.
waiting_queue {
"id": 1,
"concert_id": 1,
"status": "ACTIVE"
}
대기열의 id
는 기본 키(PK)로 자동으로 인덱스가 걸려 있으며, 대기번호는 두 가지 방식으로 구할 수 있습니다:
- 첫 번째 방식: 대기열이 콘서트별로 구분되지 않는 경우, 대기열의 가장 앞에 있는 레코드의
id
값과 자신의id
값을 비교하여 대기번호를 구할 수 있습니다. 이 방식은id
필드에 인덱스가 걸려 있어, 커버링 인덱스를 통해 빠르게 조회할 수 있습니다.- 커버링 인덱스란, 쿼리에서 필요한 모든 데이터를 인덱스에서만 조회할 수 있어 테이블에 접근할 필요가 없도록 해 성능을 향상시키는 방식입니다.
SELECT id FROM waiting_queue wq WHERE wq.status = 'WAITING' ORDER BY wq.id ASC LIMIT 1;
- 두 번째 방식: 콘서트별로 대기번호를 조회해야 하는 경우, 특정 콘서트에서 대기 중인 레코드 중 자신의
id
보다 작은 레코드를COUNT()
함수를 사용해 대기번호를 구합니다.SELECT COUNT(*) FROM waiting_queue wq WHERE wq.concert_id = :concertId AND wq.status = 'WAITING' AND wq.id < :id;
첫 번째 방식은 인덱스를 통해 빠르게 조회할 수 있지만, 두 번째 방식은 COUNT()
연산으로 인해 성능이 다소 떨어질 수 있습니다.
콘서트 예약 서비스에서는 예약 가능한 날짜 조회, 좌석 조회, 좌석 예약 등의 기능을 구현했습니다. 그중 좌석 예약 기능은 다수의 요청이 동시에 몰릴 수 있어 동시성 제어가 필요합니다. 현재 환경에서는 DB 기반의 비관적 락과 애플리케이션 레벨의 낙관적 락 중에서 비관적 락을 선택해 개발을 진행했습니다.
4주차는 기능 구현에 중점을 두고 있었으나, 구현 -> 설계 수정의 반복으로 시간이 부족했기 때문에 익숙한 방식인 비관적 락을 선택하게 되었습니다. 비관적 락 방식은 트랜잭션 대기 시간이 발생할 수 있어 성능 저하가 우려되지만, 공정성을 보장하는 장점이 있습니다. 반면, 낙관적 락은 성능 면에서는 유리하나 공정성을 보장하지 못할 수 있습니다.
특히 좌석 예약 기능에서 공정성이 필요한지에 대한 고민이 필요했고, 이에 대한 비교 내용은 해당 게시글에서 자세히 정리했습니다. JPA 비관적 락과 낙관적 락 및 재시도
이번 챕터에서는 설계와 구현을 병행하면서 어려움을 많이 겪었습니다. 설계 부족으로 인해 구현 중 설계 수정을 반복하게 되었고, 이는 개발 속도를 늦추는 원인이 되었습니다. 하지만 설계가 잘 된 부분에서는 빠르게 구현을 진행할 수 있어 설계의 중요성을 다시 한번 느끼게 되었습니다.
이전 회사에서는 설계 없이 API 명세서와 Mock API만으로 협업을 진행해 어려움을 겪은 경험이 있습니다. 설계가 제대로 이루어지지 않아 API 명세서가 빈번하게 변경되었고, 이로 인해 협업 중 API 명세를 맞추기 위한 불필요한 수정 작업이 많아져 어려움이 컸습니다. 이번 챕터를 통해 설계가 얼마나 중요한지 깨달았고, 아쉬움이 남는 부분도 많았습니다. 또한, 이번 챕터에서 에러 코드 관리, Global Exception Handler, 필터 및 인터셉터에 대한 공부와 구현을 진행했고, 관련 내용을 블로그에 정리하면서 더 깊이 있게 학습할 수 있었습니다.