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