



회원,인증,인가
상품 (생성,조회, 수정, 삭제 등)
주문 (생성, 목록조회, 취소, 주문 물품 상세 조회)
장바구니 (등록, 조회, 삭제)
트래픽 폭주 상황을 가정한 어플리케이션 기획 (ex. 티켓팅, 선착순 이벤트 등)
동시성 문제 발생 테스트 코드 작성 (실패하는 테스트 포함)
낙관적 락, 비관적 락, Lettuce, Redis 분산 락 4가지 중 1가지를 선택 → 사용하는 근거가 존재해야함 (ex : 성능 등)
락 적용 전후 테스트로 동시성 이슈 해결 여부 검증
문제 상황
여러 스레드가 동시에 하나의 주문을 취소하려 할 때, 중복 취소 발생 우려
JUnit을 이용한 테스트 코드를 작성하여 동시성 테스트 시도
잘못된 시도
cancelOrder()에서 주문 상태를 확인(if (order.getStatus() == ...))하여 중복 취소 방지 시도
예외 발생을 기대했지만, 모든 스레드가 동일한 상태(ORDERED)를 보고 실행 → 실패 없음
원인 분석
JPA는 상태 변경을 커밋 시점에 반영 → 다른 스레드가 변경 상태를 인식 못함
상태 확인도 락 내부에서 수행해야 정확성 확보 가능
해결 방향
락을 통해 하나의 스레드만 주문을 취소하게 하고
나머지 스레드는 이미 취소된 상태를 감지해 예외 처리 → 안정적인 동시성 제어
결론
문제 인식
주문 생성에는 상품에 락,
주문 취소에는 주문에 락을 거는 구조가 왜 다른가?
이유 분석
주문 생성 시:
어떤 상품을 몇 개 담았는지 모르기 때문에 → 상품별 재고 확인 필요
→ 상품 ID 기준으로 락을 걸어야 재고 충돌 방지 가능
주문 취소 시:
주문 정보에 이미 어떤 상품을 몇 개 주문했는지가 명확함
→ 주문 하나만으로 재고 복원 가능 → 주문 ID 기준으로 락
✅ 즉, 공유 자원이 다르기 때문:
생성 시 공유 자원은 상품,
취소 시 공유 자원은 주문
락이 없는 경우 재시도 구조는 유효 (경쟁 상황에서 성공 확률을 높여줌)
락이 있는 경우 실패 원인이 재고 부족이라면 재시도해도 동일 결과 → 의미 없음
문제 현상
락을 적용한 주문 취소 기능 테스트 중 중복 취소가 발생하여 재고 불일치 오류가 발생함
락을 걸었기 때문에 안전할 것이라 생각했지만 실제로는 주문 상태(OrderStatus)가 CANCELED로 인식되지 않는 문제가 원인
🧪 원인 분석
주문 상태 업데이트는 JPA의 Dirty Checking(변경 감지)에 의존하고 있었음
JPA는 트랜잭션 커밋 시점에 일괄적으로 변경 사항을 반영하기 때문에
스레드 A가 주문 상태를 CANCELED로 바꾸고 아직 커밋되지 않은 상태에서
스레드 B가 락을 획득하고 같은 주문을 조회하면 여전히 ORDERED 상태로 조회됨
결국 스레드 B도 중복 취소를 진행 → 재고가 이중으로 증가
JPA → NativeQuery로 변경
즉시 업데이트를 반영하기 위해 JPA의 변경 감지 대신 UPDATE 쿼리 사용
하지만 트랜잭션이 커밋되지 않으면 다른 트랜잭션에서는 여전히 이전 값만 조회 가능
Redisson 락 해제 시점 조정
락을 트랜잭션 커밋 후에 해제되도록 TransactionSynchronizationManager를 통해 처리
하나의 스레드가 트랜잭션을 커밋하고 나서야 다음 스레드가 락을 획득할 수 있도록 설정
⚠️ 주의점
tryLock(waitTime, leaseTime) 방식에서 leaseTime`을 너무 짧게 설정하면,
트랜잭션이 커밋되기 전에 락이 해제되어 다른 스레드가 작업을 수행할 수 있음
이로 인해 동시성 문제가 다시 발생할 수 있음**
트랜잭션 커밋 시점까지 락을 보장하려면:
lock()을 사용하여 watchdog 기반 자동 갱신을 활용하거나
leaseTime을 충분히 넉넉하게 설정해야 함
✅ 결론
JPA의 변경 감지는 트랜잭션 커밋 이전에는 다른 트랜잭션에서 확인할 수 없음
락을 걸었다고 무조건 안전한 것은 아님
락 유지 시간과 커밋 시점 간의 관계를 정확히 이해하고 조절해야 동시성 문제를 방지할 수 있음
설계 부분에서 조금 디테일하게 정해야하는 부분이 많았다고 생각함
요구사항 설정, 와이어 프레임, ERD, Entity 설정, 어디에서 캐시를 적용할지, 동시성 문제에 대해 성능 비교, 락을 어떻게 구체적으로 정할것이고 어떤 상황에 사용할 것인지
어떤 락을 사용할 것인지 (비관적 락, 낙관적 락, synchronized, lettuce, Redisson 등)
캐시 설정, 캐시 성능 비교 등
도메인별로 개발 파트를 나누니 뒤에 부분을 맡은 사람들은 앞쪽 코드에 맞춰 코드가 밀리는 상황이 발생 → 충돌을 일으킬수 있으므로 코드를 작성할 때 dto 이름, 어떤 메서드가 필요할지 생각을 많이 해야할거 같다고 느낌..