개요
콘서트 예매 시스템에서 가장 어려운 문제는 초과 예약 없이 처리량을 유지하는 것입니다. 이 프로젝트에서는 비관적 락, 낙관적 락, Redis 분산 락을 같은 시나리오로 비교하고, Redis Sorted Set + SSE 대기열과 Kafka 기반 만료·취소 이벤트까지 연결했습니다. 전략별 성공률과 p95를 k6로 다시 측정해 어떤 방식이 실제 혼합 트래픽에서 유리한지 숫자로 확인한 프로젝트입니다.
동시성 전략 비교
- ›Scenario A: 동일 좌석 100명 동시 요청에서 3가지 전략 모두 overselling 0건, 정확히 1건만 성공
- ›Scenario B: 서로 다른 좌석 50개 예약에서 비관적 락 100%, 낙관적 락 40%, Redis 분산 락 100% 성공
- ›Scenario C: Mixed Load에서 Redis 분산 락이 1,005 RPS, 읽기 p95 7ms, 쓰기 p95 6ms로 가장 안정적
- ›낙관적 락은 concert_schedule.availableSeats @Version 충돌로 성공률이 40%까지 떨어지는 병목을 확인
대기열 & 좌석 점유
- ›Redis Sorted Set + SSE로 대기열 순번을 스트리밍하고, 100등 이내 사용자에게 입장 토큰 발급
- ›입장 토큰은 1회 사용 후 소멸하고 TTL 5분으로 만료 처리
- ›좌석 임시 점유는 Redis TTL 5분으로 관리하고, 미결제 예매는 스케줄러가 자동 해제
- ›ShedLock으로 다중 인스턴스 환경에서도 만료 스케줄러가 중복 실행되지 않도록 설계
이벤트 & 복구
- ›Kafka reservation.completed / reservation.cancelled 이벤트로 결제 완료와 취소·만료 흐름 분리
- ›seat-release consumer가 만료·취소 좌석을 복구하고 Redis 재고를 되돌림
- ›manual commit + 3회 재시도 + DLT로 실패 이벤트를 격리
- ›Redisson MultiLock으로 다좌석 예매 시에도 분산 환경 잠금을 일관되게 유지
검증 환경
- ›PostgreSQL 16, Redis 7, Kafka KRaft, Docker Compose 기반 로컬 통합 환경
- ›Testcontainers와 k6 시나리오 A/B/C로 정합성·성공률·레이턴시를 비교 측정
- ›Apple M4, HikariCP 10, Tomcat max-threads 200 환경에서 성능 결과 수집
- ›성능 결과와 설계를 docs/PERF_RESULT.md, docs/DESIGN.md로 문서화
아키텍처
전체 아키텍처▼
ERD
전체 ERD▼
시퀀스 다이어그램
대기열 진입▼
예매 & 결제▼
만료 좌석 해제▼
문제 원인
- 01같은 좌석에 동시 요청이 몰리면 overselling 없이 정합성을 지켜야 했습니다.
- 021만 명 수준의 동시 접속을 가정하면 대기열 없이 예매 API를 열기 어렵습니다.
- 03결제하지 않은 예매가 남아 있으면 좌석이 계속 잠겨 재고가 복구되지 않습니다.
- 04분산 환경에서는 락, 이벤트 처리, 스케줄러가 모두 다중 인스턴스를 전제로 동작해야 했습니다.
해결 과정
- 01비관적 락, @Version + Spring Retry 기반 낙관적 락, Redis + Redisson 분산 락 3가지 전략을 구현해 같은 시나리오에서 비교했습니다.
- 02Redis Sorted Set + SSE로 대기열을 만들고, 100등 이내 사용자에게만 입장 토큰을 발급했습니다.
- 03Redis TTL 5분과 ReservationExpirationScheduler를 연결하고, ShedLock으로 스케줄러 중복 실행을 막았습니다.
- 04Kafka reservation.completed / reservation.cancelled 이벤트, manual commit, 3회 재시도, DLT로 복구 경로를 구성했습니다.
결과
- 01Scenario A Hot Seat Contention: 3가지 전략 모두 overselling 0건, 동일 좌석 100명 동시 요청에서 정확히 1건만 성공했습니다.
- 02Scenario B Distributed Reservation: 비관적 락 100%(50/50), Redis 분산 락 100%(50/50), 낙관적 락 40%(20/50) 성공률을 확인했습니다.
- 03Scenario C Mixed Load: Redis 분산 락 기준 총 1,005 RPS, 읽기 p95 7ms, 쓰기 p95 6ms를 기록했습니다.
- 04Redis 대기열로 100등 이내 사용자에게 토큰을 발급하고, 만료·취소 좌석은 Kafka consumer로 자동 복구했습니다.