문제 해결 포트폴리오 / 콘서트 예매 / 예약 정합성
측정 완료Concert Booking · Deep Dive 1/2
동일 좌석 오버셀링 0건 검증
100개 동시 예매 요청에서 Queue Token·좌석 락·Idempotency-Key로 success 1, fail 99를 확인했습니다.
동시 요청
100
결과
success 1 / fail 99
오버셀링
0
해결 키워드Queue Token좌석 락Idempotency-Key
- 프로젝트
- Concert Booking
- 참여
- 개인 / BE 1
- 기술
- Java · Spring Boot · PostgreSQL · Redis · Kafka
문제 구간 아키텍처
핵심 설계 판단
- PostgreSQL을 좌석/예약의 최종 기준 데이터로 둡니다.
- Queue Token은 대기열 우회 요청을 제한하고, Idempotency-Key와 Seat Lock은 Reservation Tx 안에서 처리합니다.
- Redis stock은 빠른 조회/제어용이며, 불일치는 DB 기준 reconciliation 대상으로 둡니다.
문제
- 동일 좌석에 여러 사용자가 동시에 접근하면 읽기-수정-쓰기 사이 race condition으로 오버셀링이 발생할 수 있었습니다.
- 네트워크 timeout과 client retry는 중복 예약 또는 중복 결제로 이어질 수 있었습니다.
- Redis 재고와 DB 예약 상태가 어긋날 때 어떤 데이터를 최종 기준 데이터로 삼아 복구할지 명확해야 했습니다.
해결
- Queue token을 userId + scheduleId에 바인딩해 대기열 우회 요청을 제한했습니다.
- Reservation transaction 안에서 Idempotency-Key 확인, 좌석 락, 예약 생성을 함께 처리했습니다.
- DB commit 이후 발행할 이벤트는 Outbox에 남기고 Redis stock은 PostgreSQL 기준 reconciliation 대상으로 두었습니다.
결과
- 동일 좌석 100개 동시 요청에서 success 1, fail 99, overselling 0을 기록했습니다.
- 서로 다른 좌석 50개 예약에서 pessimistic lock과 Redis distributed lock 모두 50/50 성공을 확인했습니다.
- 예약/결제 idempotency, race condition, DLT replay, stock reconciliation을 Testcontainers 시나리오로 검증했습니다.
검증 근거
동일 좌석 경합
측정 완료100 concurrent requests -> success 1, fail 99, overselling 0
분산 좌석 예약
측정 완료50명 서로 다른 좌석 예매 -> pessimistic 50/50, Redis distributed lock 50/50
Testcontainers 검증 시나리오
시나리오 검증reservation/payment idempotency, race condition, DLT replay, stock reconciliation 검증
구현 포인트
- 좌석 경합은 DB transaction 안의 락과 idempotency check가 같은 경계에 있도록 정리했습니다.
- Redis는 빠른 조회와 queue 역할을 맡지만 좌석/예약 상태의 최종 기준 데이터로 두지 않았습니다.
- 이벤트 발행 의도는 Outbox에 남기되, 이 사례의 핵심 검증은 좌석 경합과 중복 예약 방지에 맞췄습니다.
세부 테스트, guard, raw artifact는 GitHub README와 docs에 정리했습니다.
GitHub 근거 보기