콘서트 예약 서비스
2024-10-05 — 2024-11-30
동일 좌석·사용자 지갑 동시성 테스트로 좌석에는 낙관적 락, 결제·충전에는 사용자 단위 Redisson 락을 적용하고, 공개 k6 보고서에서 캐시 스탬피드 p95를 5.74초에서 676ms로 낮춘 콘서트 예약 프로젝트

시스템 아키텍처
문제 해결 과정
동일 좌석에 100개 thread가 동시에 예약을 시도할 때 중복 예약 위험
ConcertSeat @Version 낙관적 락과 100-thread Spring 통합 테스트로 충돌 경로 검증
테스트 종료 후 예약 행 1개와 reserved 상태를 assertion으로 확인
포인트 충전과 결제의 동시 실행 시 잔액 정합성 붕괴
Redisson 분산 락(userWalletLock:{userId})으로 사용자 단위 충전·결제 경로를 직렬화
100-thread 충전 테스트에서 최종 잔액을 기대 합계와 비교하고 결제 테스트에서 단일 결제 상태를 확인
예약 부하 테스트에서 캐시 만료 구간의 stampede와 p95 5.74s 지연 관측
예약 시작 3분 전 캐시 워밍과 TTL 2분→5분 조정을 적용
공개 보고서 기준 예약 p95 5.74s → 676ms, 설정 SLO 1s 이내
결제 이벤트의 저장·발행·실패 재시도 상태를 하나의 동기 호출에서 분리해야 했다
Kafka Transactional Outbox 패턴으로 결제 TX 내 PENDING outbox_event를 저장하고 scheduler가 비동기 발행
결제 트랜잭션의 PENDING outbox 생성과 발행 실패 상태·재시도 경로를 코드와 테스트로 분리
프로젝트 설명
공개 저장소의 100-thread 통합 테스트는 동일 좌석 요청에서 예약 행 하나만 남는지, 사용자 단위 Redisson 락 아래 충전·결제 결과가 기대값과 일치하는지 검증한다. 결제 트랜잭션은 PENDING outbox 행을 만들고 scheduler가 Kafka 발행·실패 재시도를 맡는다. k6 예약 시나리오는 3,000 requests/s를 설정했지만 최대 VU는 2,400이므로 이를 달성 처리량이나 동시 사용자 수로 표현하지 않는다. 공개 보고서에 기록된 예약 오류율 0.64%와 p95 5.74s, 캐시 워밍·TTL 조정 후 p95 676ms만 관측 결과로 사용한다.
주요 내용
- 동일 좌석 100-thread 통합 테스트에서 예약 행 1개 확인
- 사용자 지갑·결제를 Redisson 사용자 단위 락으로 직렬화
- 캐시 워밍·TTL 조정 후 공개 보고서 p95 5.74s → 676ms
- 결제 TX의 PENDING outbox와 scheduler 발행·재시도 경로 구현
- k6 설정값 3,000 requests/s와 최대 2,400 VU를 관측 성과와 분리
성과 지표
| 성과 지표 | 이전 | 이후 |
|---|---|---|
| 예약 p95 | 5.74s | 676ms (캐시 워밍 + TTL 2분→5분) |
| 예약 오류율 | - | 0.64% (예약 시나리오 394건 실패 기록) |
| 대기열 p95 | - | 62.5ms (대기열 시나리오 오류율 0%) |
기술 선택 근거
- ▶좌석 선점에 @Version 낙관적 락: 100-thread 동일 좌석 통합 테스트가 예약 행 1개를 assertion
- ▶결제·충전에 Redisson 사용자 단위 락: 100-thread 테스트가 결제 상태와 최종 잔액을 기대값과 비교
- ▶Redis Sorted Set 대기열: 입장 순서와 만료 상태를 score로 관리
- ▶결제 TX에서 PENDING outbox 생성 후 scheduler가 발행 실패 상태와 재시도 경로를 관리
깨달은 점
- •동일 좌석과 사용자 지갑의 경합 범위에 따라 낙관적 락과 사용자 단위 분산 락을 구분
- •k6 설정값·최대 VU와 보고서의 관측 결과를 분리해 성능 수치의 범위를 명확히 기록
- •캐시 워밍과 TTL 조정은 공개 보고서의 예약 p95 개선 범위로만 해석
- •결제 outbox의 PENDING·발행 실패·재시도 상태를 분리해 비동기 경계를 명시