🎟️ 선착순 강의에서 공연 티켓팅으로 확장한 이유

2026. 4. 17. 02:15·프로젝트/공연 티켓팅 프로젝트

1️⃣ 문제 인식 — “선착순”만으로는 부족했다

처음에는 단순한 선착순 강의 신청 시스템을 구현했다.
하지만 구현을 진행하면서 한 가지 한계를 느꼈다.

“선착순이라는 개념은 있지만, 실제 서비스 도메인과 연결되어 있지 않다”

예를 들어:

  • 재고가 왜 중요한지
  • 결제 실패 시 어떤 문제가 발생하는지
  • 동시 요청이 몰릴 때 어떤 문제가 생기는지

이런 부분이 드러나지 않았다.

그래서 단순 선착순을 넘어서
👉 공연 티켓팅 도메인으로 확장하게 되었다.


2️⃣ 바로 구현하지 않고, 먼저 실험을 진행한 이유

처음부터 Redis, Outbox 같은 기술을 사용하는 대신
다음 기준을 세웠다.

❗ “기술은 선택하는 것이 아니라, 검증하는 것이다”

그래서 핵심 설계 포인트마다
실험을 먼저 진행한 뒤 실제 구현에 반영했다.


3️⃣ 동시성 핵심 — 재고 선점 방식 실험 (E1)

공연 티켓팅에서 가장 중요한 문제는 이것이다.

❗ 좌석보다 더 많이 예매되면 안 된다 (초과 발급 금지)

이를 해결하기 위해 3가지 방식을 비교했다.

  • Redis DECR
  • MySQL Pessimistic Lock
  • MySQL Optimistic Lock

실험 조건

  • 동시 요청: 100명
  • 재고: 50개

결과

  • Redis DECR → 초과 발급 0건 + 가장 높은 처리량
  • Pessimistic Lock → 안전하지만 성능 저하
  • Optimistic Lock → 충돌 증가로 재시도 폭발

결론

👉 Redis 기반 재고 차감 방식 채택


4️⃣ 하지만 단순 Redis DECR로는 부족했다

초기에는 아래처럼 단순하게 생각했다.

요청 → Redis DECR → 성공하면 예매

하지만 실제 테스트를 진행하면서 문제가 발생했다.

❗ “누가 먼저 요청했는지 보장되지 않는다”

즉,

  • 단순 DECR은 “재고”만 관리할 뿐
  • 요청 순서(선착순)를 보장하지 않는다

5️⃣ 해결 — Redis 대기열 + 입장권(admitted) 구조 도입

이 문제를 해결하기 위해 구조를 다음과 같이 변경했다.

전체 흐름

Queue 진입 → 대기열 순서 관리 → 입장 허용(admitted) → 예매 진행

핵심 아이디어

  • Redis Sorted Set으로 대기열 구성
  • score = timestamp (요청 순서 보장)
  • 상위 N명만 입장 허용
  • 입장 허용된 사용자에게 “admitted token” 발급

실제 구현 흐름

1. 대기열 진입

ZADD queue:concert:{id} timestamp userId

2. 입장 허용 (Lua Script)

  • 상위 N명 추출
  • admitted 키 발급 (TTL 포함)
SETEX admitted:concert:{id}:user:{uid} 600 1
 

3. 예매 요청 시 검증

admitted 키 존재 여부 확인
→ 없으면 예매 불가

👉 이 구조를 통해

  • 선착순 보장
  • 재고 초과 방지
  • 서버 부하 제어

를 동시에 해결할 수 있었다.


6️⃣ 결제 단계 — 멱등성 처리 (E3)

결제 API에서 중요한 것은 다음이다.

❗ 같은 요청이 여러 번 들어와도 한 번만 처리되어야 한다

실제 테스트 중

  • 네트워크 재시도
  • 사용자 중복 클릭

으로 인해 동일 요청이 여러 번 들어오는 상황이 발생했다.

해결 방식

Redis의 SETNX를 활용해 멱등성을 보장했다.

SETNX payment:{idempotencyKey}

 

  • 최초 요청만 성공
  • 이후 요청은 차단

실제 구현은 더 강화했다

단순 Redis만 사용하는 것이 아니라

  • Redis SETNX
  • DB unique constraint
  • 기존 결제 여부 조회

👉 3중 방어 구조로 설계


7️⃣ 결제 실패 문제 — 보상 트랜잭션 (E4)

결제는 외부 시스템이기 때문에
항상 실패 가능성이 존재한다.

문제는 이 시점이다.

❗ 이미 좌석은 차감됐는데 결제가 실패한 경우

이 경우

  • 좌석이 영구적으로 사라지는 문제가 발생한다

8️⃣ 해결 — Outbox 패턴 도입

보상 로직을 안정적으로 처리하기 위해
Outbox 패턴을 도입했다.

흐름

결제 실패 → Outbox 테이블에 이벤트 저장
→ Scheduler가 polling
→ 보상 처리 수행 (예매 취소 + 재고 복구)

 

실제 처리

  • Booking.cancel()
  • Concert.bookedCount 감소
  • 상태 업데이트

왜 Outbox를 선택했는가

  • 트랜잭션 내에서 이벤트 저장 → 유실 방지
  • 재시도 가능 → 결국 처리됨 (Eventually Consistent)
  • 안정성 확보

9️⃣ 최종 아키텍처

실험과 구현을 통해 다음 구조로 정리되었다.

  • 재고 및 선착순 → Redis Queue + admitted token
  • 멱등성 → Redis SETNX + DB 보완
  • 보상 처리 → Outbox 패턴 + Scheduler

🔟 정리

이 프로젝트에서 가장 중요하게 생각한 것은
“어떤 기술을 썼는가”가 아니었다.

❗ “왜 이 기술을 선택했는가”

  • Redis는 빠르기 때문에 사용한 것이 아니라
    → 동시성 문제를 해결하기 위해 선택
  • SETNX는 편해서가 아니라
    → 중복 결제를 막기 위해 선택
  • Outbox는 트렌드라서가 아니라
    → 보상 유실을 막기 위해 선택

'프로젝트 > 공연 티켓팅 프로젝트' 카테고리의 다른 글

🎟️ Docker-compose up 한 번으로 실행되는 백엔드 만들기  (1) 2026.05.18
🎟️ MySQL 인덱스 실험: 예약 만료 배치 쿼리 성능 개선 (10만 건 실험)  (1) 2026.05.13
🎟️ 결제 API는 정말 중복 결제를 막을 수 있을까?  (1) 2026.04.27
🎟️ 예약 API 동시성 테스트 (다중 사용자 경쟁) 결과 보고  (1) 2026.04.27
🎟️ Redis 입장권 기반 예약 시스템은 정말 중복 예약을 막을까?  (0) 2026.04.27
'프로젝트/공연 티켓팅 프로젝트' 카테고리의 다른 글
  • 🎟️ MySQL 인덱스 실험: 예약 만료 배치 쿼리 성능 개선 (10만 건 실험)
  • 🎟️ 결제 API는 정말 중복 결제를 막을 수 있을까?
  • 🎟️ 예약 API 동시성 테스트 (다중 사용자 경쟁) 결과 보고
  • 🎟️ Redis 입장권 기반 예약 시스템은 정말 중복 예약을 막을까?
hak0622
hak0622
개발하면서 “이게 뭐지?”라는 순간마다 궁금한 점을 바탕으로 정리한 개발 블로그입니다.
  • hak0622
    궁금한 개발 이야기 Why?
    hak0622
  • 전체
    오늘
    어제
    • 분류 전체보기 (81)
      • 공부 (36)
        • 1. 자바 ORM 표준 JPA 프로그래밍 - 기본.. (35)
        • 시험 (1)
      • 프로젝트 (45)
        • 스프링 부트 3 백엔드 개발자 되기 Blog + .. (35)
        • 공연 티켓팅 프로젝트 (10)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
🎟️ 선착순 강의에서 공연 티켓팅으로 확장한 이유
상단으로

티스토리툴바