Skip to content

[BUG]: 예약/결제 동시성 취약점 수정 (레이스 컨디션 및 오버부킹) #157

Description

@zerochani

🐛 버그 설명

예약 생성, 결제 확인, 예약 취소 로직에서 동시에 여러 요청이 들어올 때 레이스 컨디션이 발생할 수 있습니다. 이로 인해 오버부킹, 확정된 예약의 자동 취소, 중복 환불 API 호출 등의 심각한 데이터 정합성 문제가 발생할 수 있습니다.


🔍 원인 분석

1. createBooking() — 비관적 락이 BookingTable을 보호하지 않음

파일: BookingCommandServiceImpl.java:73-86

StoreTablePESSIMISTIC_WRITE 락을 걸지만, 중복 예약 확인 쿼리(findReservedTableIds)는 Booking/BookingTable 테이블을 조회하며 이 테이블에는 락이 없습니다.
MySQL REPEATABLE_READ 스냅샷 타이밍에 따라, 다른 스레드가 커밋한 예약이 보이지 않아 동일 테이블/시간대에 이중 예약이 발생할 수 있습니다.

2. BookingScheduler — 확정된 예약을 취소할 수 있음

파일: BookingScheduler.java:27-46

  • jakarta.transaction.Transactional 오용 (Spring의 @Transactional이 아님)
  • 스케줄러가 PENDING 목록 조회 후, 결제 확인 스레드가 동시에 CONFIRMED로 변경하면 스케줄러가 해당 예약을 CANCELED로 덮어씌울 수 있음

3. confirmPayment() / cancelBooking() — Booking 락 없음

파일: BookingCommandServiceImpl.java:156, BookingCommandServiceImpl.java:185
파일: PaymentService.java:118-122

Booking 엔티티에 @Version 필드가 없어 두 경로(BookingCommandServiceImplPaymentService)에서 동시에 booking.confirm()을 호출하면 둘 다 커밋될 수 있습니다.

cancelBooking()은 상태 확인과 Toss 환불 API 호출 사이에 갭이 있어 중복 환불 호출이 가능합니다.


✅ 작업할 내용

🔴 CRITICAL — 즉시 수정

  • DB 유니크 제약 추가: booking_table 테이블에 (store_id, booking_date, booking_time, table_id) 유니크 제약 추가 — 애플리케이션 락 우회 시 최후 방어선
  • BookingScheduler.java:27 jakarta.transaction.Transactionalorg.springframework.transaction.annotation.Transactional 변경
  • BookingScheduler.java:44 취소 처리 전 상태 재확인 로직 추가 (PENDING 여부 검증)
  • Booking 엔티티에 @Version 추가 — 낙관적 락으로 동시 상태 변경 감지

🔴 HIGH — 단기 수정

  • BookingRepository에 락 메서드 추가: findByIdWithLock(Long id) (@Lock(PESSIMISTIC_WRITE)) 구현
  • BookingCommandServiceImpl.confirmPayment():156 findByIdfindByIdWithLock 교체
  • BookingCommandServiceImpl.cancelBooking():185 findByIdfindByIdWithLock 교체
  • BookingCommandServiceImpl.cancelBookingByOwner():223 동일하게 락 적용

🟡 MEDIUM — 추가 개선

  • PaymentService.confirmPayment() Toss 외부 API 호출을 트랜잭션 바깥으로 분리 (커넥션 풀 고갈 방지)
  • PaymentService.cancelPayment() 취소 전 Payment 상태 검증 및 멱등성 체크 추가
  • 각 수정 사항에 대한 단위 테스트 작성

🙋🏻 참고 자료

  • 분석 대상 파일:
    • domain/booking/service/BookingCommandServiceImpl.java
    • domain/booking/service/BookingScheduler.java
    • domain/booking/repository/BookingRepository.java
    • domain/payment/service/PaymentService.java
    • domain/payment/entity/Payment.java
  • MySQL InnoDB MVCC 및 REPEATABLE_READ 격리 수준 문서
  • Spring Data JPA @Lock 어노테이션 공식 문서

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions