개발지식 먹는 하마 님의 블로그
[내일배움캠프 68일차] 좌석 상태 관리 - 트러블 슈팅 본문
티켓 예매에서 가장 중요한 부분은 좌석의 상태를 관리하는 것이라고 생각했고
이 부분을 꼼꼼하게 관리하기 위한 부분에 집중했다.
그렇다 보니 구조가 조금씩 리팩토링 되었다.
이번 글에서는 그 과정에 대해 정리하고자 한다.
💡트러블 슈팅 - Redis 데이터 형태 구조 변경
🟩1차 구현
Redis의 TTL 기능을 통해 좌석의 예매 상태를 관리하고자 하였다.
회차별 좌석 상태를 나타내는 SeatScheduleInfo의 Id를 Key로,
좌석의 상태를 Value로 설정하여 데이터를 Redis에 저장한다.
Available(예매 가능) 상태였던 좌석이 Selected(선점됨)로 변경된 후,
5분 이내에 Hold(결제 진행 중)가 되지 않으면 데이터 삭제 후, 다시 Available 상태로 설정되도록
TTL을 5분으로 설정하였다.
❗문제점 - 누가 선점했는지는 어떻게 아는가?
예매 정보 생성 시, 어떤 유저가 좌석을 선점했는지를 알아야 한다.
따라서, 유저의 Id 값이 필요하다!
-> 유저의 Id를 Key로 설정한 데이터 형식으로 바꿔보자!
🟩2차 구현
데이터 형식을 아예 변경해 보았다.
공연 회차 Id와 유저 Id를 Key로 사용하고
SET 형태의 데이터로 회차별 좌석 상태 Id를 저장한다.
Set 형식을 사용한 이유
예매 정책을 확정 짓지 않았었기 때문에 한 유저가 여러 좌석을 선택할 수 있을 때를 가정하였다.
(추후 논의를 통해 1인 1매 정책으로 확정 지었기 때문에 Set을 사용할 필요가 없어졌다.)
새 좌석을 선택할 때마다 TTL은 5분으로 업데이트한다.
❗문제점 - 그럼 좌석별 상태는 어떻게 확인하는가?
이 방법은 누가 좌석을 선점했는지 확인하는지에 적합한 구조이기 때문에
좌석의 상태를 조회하기 위해서는 해당 좌석의 Id가 누군가의 Value 안에 있는지 조회하면서 찾아야 했다.
1번 좌석의 상태를 변경하기 위해 유저 전체를 조회하고
2번 좌석의 상태를 변경하기 위해 유저 전체를 조회하고...
좌석 수 * 유저 수만큼 Redis 조회를 해야 하는 구조가 된 것이다.
이때, 유저의 수가 매우 많은 경우 성능에 문제가 발생한다.
또한 좌석 기준으로 빠르게 상태를 확인하기 어렵다.
-> 좌석 상태는 유저와는 별개로 관리하자!
🟩3차 구현
예매 정보 생성을 위한 데이터와 좌석 상태 관리를 위한 데이터는 분리되어야 한다.
(1개로 해결하고 싶었는데...)
2차 구현 구조를 일단 계속 유지하는 방향으로 진행하였다.
회차 Id를 Key로 하고 Value에서 Hash로 회차에 해당하는 회차별 좌석 정보의 상태를 관리하는 것이다.
❓왜 좌석 상태를 이전과 다르게 Hash로 관리하는가?
1차 구현에서는 회차별 좌석 정보 Id를 Key로 해서 좌석의 상태를 관리했는데
3차 구현에서 회차 Id를 Key로 하고 HashMap 형태의 Value로 회차별 좌석 정보의 상태를 관리한다.
그렇게 구현한 이유는 Redis에 보내는 조회 요청 수를 줄이기 위해서이다.
기존에는 좌석의 상태를 조회하기 위해서는 회차에 해당하는 회차별 좌석 정보 Id를 전부 가져온 후,
반복문을 돌며 각 Id마다 Redis에 조회 요청을 보내게 된다.
for (String seatId : seatIds) {
String status = redisTemplate.opsForValue().get(seatId);
}
이를 Hash 구조로 바꾸게 되면 아래와 같이 multiGet과 같은 메서드로 1번의 요청만으로 조회를 할 수 있게 된다.
List<Object> results = redisTemplate.opsForHash().multiGet(redisKey, new ArrayList<>(seatIds));
❗트레이드오프 - 적은 Key 종류 Vs 조회 부하 감소
이런 과정을 거치게 된 것은 내가 관리하는 키의 종류를 더 이상 늘리고 싶지 않았기 때문에 여러 가지를 시도하다가 발생한 일이다. 그러나 트레이드오프를 해야 하는 순간이 찾아왔다.
결국 현재 구조에서는 위에서 언급했다시피 어떤 데이터든 간에 만료 여부를 확인하기 위해서는
그 데이터 전체를 확인해야 한다는 단점이 있다.
예를 들어 100명의 유저가 있을 때, 특정 유저의 키가 만료되었는지의 여부를 확인하기 위해서
최대 100개에 대한 데이터 조회를 하게 된다는 것이다.
이때, 유저의 수가 1000만 명 2억 명처럼 계속 늘어난다면?
엄청난 횟수의 조회를 하게 된다.
여러 Key를 어찌어찌 통합해서 적은 개수로 관리하느냐
VS
관리해야 하는 key 값을 늘리더라도 조회의 부하를 줄이느냐
이 두 선택지 앞에서 나는 후자를 선택하였다.
티켓팅 페이지에서 좌석의 상태는 특히나 조회가 많이 발생하는 영역이기 때문에
조회 부하를 줄이는 것이 더 옳은 선택이라고 생각했기 때문이다.
그리하여~~~
✅4차 최종 구현
위와 같은 고민의 시간을 겪고 총 5개의 Key로 데이터를 관리하고자 하였다.
1. 회차별 좌석 목록
Key : 회차
Value : Hash(회차별 좌석 정보 - 상태)
1회차 :
1회차 좌석 1 - Available,
1회차 좌석 2 - Hold
.
.
.
2. 유저 좌석 선점 정보
Key : 유저, 회차
Value : 회차별 좌석 정보
유저 1, 1회차 - 1회차 좌석 1
유저 3, 2회차 - 2회차 좌석 3
3. 선점된 좌석 내역
Key : SeatOccupy
Value : 회차별 좌석 정보
TTL - Selected : 5분, Hold : 60분
유저와는 관계없이 선점되었는지 아닌지만 다룬다.
좌석 선점 상태가 변할 때마다 상태에 해당하는 TTL이 적용된다.
4, 5. 상태별 TTL 관리
앞서 설명했던 2개의 Key 외에 '만료 시간 TTL이 필요한 상태를 관리하는 Key' 2개를 추가하였다.
- expires:selected
- expires:hold
Sorted Set 형식의 Value에 회차별 좌석 정보 Id, Score로 만료 시각을 설정한다.
그리고 만료 시각을 정렬 기준으로 사용한다.
좌석 상태 관리 로직
'내일배움캠프 (CS25)' 카테고리의 다른 글
[내일배움캠프 70일차] Java 기반 웹 크롤링 (1) | 2025.05.29 |
---|---|
[내일배움캠프 69일차] nullnull티켓 회고 (0) | 2025.05.28 |
[내일배움캠프 65일차] Github CI 트러블 슈팅 (0) | 2025.05.22 |
[내일배움캠프 63일차] nullnullTicket 동시성 제어 분산락 구현 (0) | 2025.05.19 |
[내일배움캠프 62일차] 동시성 제어 티켓팅 프로젝트 설계 (0) | 2025.05.16 |