구름 딥 다이브 프로젝트에서 스타벅스 앱 클론을 개발하던 중,
주문번호(order number) 중복 에러를 발견했습니다.
[ERROR: duplicate key value violates unique constraint "orders_order_number_key”]
이 글은 문제 해결 과정을 기록한 블로그 포스트입니다.
| ID(PK) | 메뉴 | 고객 주문번호 | 매장ID(FK) |
|---|---|---|---|
| 1 | [아메리카노 1잔, 라떼 2잔] | A-10 | 100 |
| 2 | [아메리카노 3잔, 모카 1잔] | A-10 | 101 |
| 3 | [자바칩 프라푸치노 2잔] | B-33 | 103 |
order_daily_counter 테이블의 카운터 값을 이용해 주문번호 생성
@Transactional
public String generateOrderNumber(OrderCreateRequest request) {
LocalDate today = LocalDate.now();
OrderDailyCounterId id = new OrderDailyCounterId(today, request.storeId());
OrderDailyCounter counter = orderDailyCounterRepository.findById(id)
.orElseGet(() -> new OrderDailyCounter(id, 0));
counter.increment();
orderDailyCounterRepository.save(counter);
return "A-" + counter.getCount();
}
generateOrderNumber()에서 이미 저장을 하는데
왜 주문번호가 중복(동일)하게 나올까? 🧐
- 백엔드: Spring Boot, JPA(Hibernate)
- 트랜잭션 관리: Spring의 @Transactional
- DB: PostgreSQL
- 운영환경: JVM 기반, Prometheus 지원
| 방법 (영문) | 요약 | 장점 | 단점 |
|---|---|---|---|
| 🟢 DB Atomic Operation(원자적 연산) | UPDATE ... SET count = count + 1과 같이 DB 명령어를 활용 | 구현 간단, DB가 동시성 제어 | DB 부하 집중, 트랜잭션 충돌 가능성 |
| 🔵 DB Sequence | 데이터베이스의 시퀀스 객체를 사용하여 고유값 생성 | 고유값 보장, Race Condition 없음 | 날짜별 리셋 등 비즈니스 로직과 다를 수 있음 |
| 🟣 Optimistic Lock(낙관적 락) | @Version 필드로 버전 충돌 감지 후 재시도 | 트래픽 낮으면 성능 유리, Deadlock 위험 낮음 | 충돌 많으면 재시도 비용 증가 |
| 🟡 Pessimistic Lock(비관적 락) | SELECT FOR UPDATE 구문으로 데이터 읽기 시점에 락을 선점 | 충돌 발생률이 높을 때 데이터 무결성 확실히 보장 | 락 대기로 인해 성능 저하, Deadlock 위험 높음 |
| 🟠 Redis INCR(증가 연산) | Redis의 INCR (Atomic Increment) 명령 활용 | 고성능, 확장성 우수 | Redis 장애 시 대체 로직 필요, 영속성 주의 |
| 🟤 DB Upsert(삽입/갱신) | INSERT ... ON CONFLICT DO UPDATE와 같은 구문 활용 | 단일 쿼리로 동시성 강함 | DB별 문법 상이, 트랜잭션 내 주의 필요 |
실제 적용 예시, 해결법 상세 구현, 트러블슈팅 경험 등은 별도 포스트로 이어집니다!