Concert Booking / Engineering case

같은 좌석을 누른 두 사람에게 다음 행동까지 돌려주기

대기열 입장, 동일 요청 재시도, 좌석 상태 변경을 한 장치에 몰지 않고 각각의 경계가 책임지게 했습니다.

GitHub에서 코드 보기 ↗
User impact

한 사용자가 좌석을 먼저 확정해도 다른 사용자는 모호한 실패 화면이 아니라 최신 좌석표로 돌아가 다시 선택할 수 있어야 합니다.

01

어떻게 깨지는가

  • 두 요청이 같은 AVAILABLE 상태를 읽으면 예약이 두 건 생성될 수 있습니다.
  • 입장 토큰 응답을 잃은 재시도가 이미 queue에서 제거된 사용자를 복구하지 못할 수 있습니다.
  • 동일 버튼의 중복 클릭이 새로운 예약 의도로 처리될 수 있습니다.

02

지켜야 할 경계

  • Queue Token은 일정과 사용자에 묶여야 합니다.
  • 좌석의 최종 기준은 Redis가 아니라 DB transaction입니다.
  • 같은 key의 다른 payload는 정상 재시도가 아니라 conflict입니다.
좌석 경합의 세 경계진입·중복 요청·최종 좌석 상태를 서로 다른 장치가 맡습니다.admitreservevalidatetransactioncommitBooking intentstable idempotency keyQueue Tokenuser + scheduleReservationclaim + transactionSeat locksingle winnerDB constraintslast defense
  1. Queue Token이 일정별 입장 권한을 확인합니다.
  2. Idempotency-Key가 같은 예약 의도의 재시도를 찾습니다.
  3. 좌석 lock과 DB constraint가 최종 한 건을 확정합니다.
진입·중복 요청·최종 좌석 상태를 서로 다른 장치가 맡습니다.
좌석 경합의 세 경계
좌석 경합의 세 경계진입·중복 요청·최종 좌석 상태를 서로 다른 장치가 맡습니다.admitreservevalidatetransactioncommitBooking intentstable idempotency keyQueue Tokenuser + scheduleReservationclaim + transactionSeat locksingle winnerDB constraintslast defense

03

선택한 설계

  • Redis Lua에서 기존 token 확인·rank 검증·token 저장·queue 제거를 한 번에 처리합니다.
  • DB lock과 unique constraint가 최종 좌석 변경을 한 건으로 수렴시킵니다.
  • 클라이언트는 예약 의도 하나에 같은 Idempotency-Key를 유지합니다.

04

감수한 trade-off

  • DB를 최종 기준으로 두어 write 경합 비용을 감수하지만 복구 기준이 명확합니다.
  • Queue와 reservation의 책임이 나뉘어 상태가 늘지만 실패 원인을 구분할 수 있습니다.

05

어떻게 확인했는가

  • 동일 좌석 10개 동시 요청이 성공 1건으로 수렴합니다.
  • 동일 key replay는 기존 예약을 반환하고 Queue Token 응답 유실 뒤 재발급도 같은 token으로 복구됩니다.
  • 브라우저 E2E에서 패자는 최신 좌석표로 돌아갑니다.

Replay the boundary

글 대신 단계별로 따라보기

Flow 재생

Sources

코드와 테스트로 돌아가기

  • 동일 좌석 경합 통합 테스트

    공개 저장소에서 확인
    근거 ↗
  • 예약 멱등성 통합 테스트

    공개 저장소에서 확인
    근거 ↗
  • Queue Token 멱등 발급·재진입·만료 상태 테스트

    공개 저장소에서 확인
    근거 ↗
  • 예약 완주·좌석 패자 복구·응답 유실 브라우저 E2E

    공개 저장소에서 확인
    근거 ↗
한계와 다음 확인
  • 공개 검증은 로컬 Testcontainers와 브라우저 E2E 범위입니다.
  • 세 가지 lock 전략의 production 처리량 비교를 주장하지 않습니다.