
기존 팀 프로젝트에서 주문생성과, 주문 취소 시 어느 정도 동시성 문제를 해결했다고 생각하였으나 재시도 과정에서 여러 의문점들이 생기고 복잡한 구조에 대한 의문이 생겨 추가적인 리팩토링 작업을 진행했습니다.
위의 링크는 이전에 기록해 놓은 팀 프로젝트에 내용과 트러블슈팅이 적혀 있습니다. 링크 안에 있는 내용이 완전히 틀린 내용만 존재하는 것이 아니며 프로젝트 진행 과정이라고 봐주시면 좋을 거 같습니다.
동시성 문제 잘못된 해결 사례 내용에는 주문생성과 주문 취소 시 락 대상의 차이에 대한 내용이 나오는데 주문 생성 시에는 상품에 락을 걸고 주문 취소 시에는 주문자체에 락을 거는 방법을 사용했습니다.
주문 취소할 때는 이미 주문이 생성되어 있으니 주문 정보에 상품과 수량이 다 기록되어 있다고 생각하였습니다.
주문 취소 시 주문 하나만 확인하면 다른 주문들과 충돌이 발생하지 않을거라는 논리적 추론에서 착각이 생겼습니다.
하지만 실제 공유 자원은 상품 재고입니다.
동일 상품이 여러 주문에 포함되어 있고 동시에 여러 주문 취소가 발생하면 같은 상품 재고를 수정해야하는 상황이 발생합니다
따라서 주문 취소 시에도 주문에 락을 거는 것이 아닌 상품에 락을 걸어야합니다.
결론 : 주문 생성, 주문 취소 시 락 대상 → 상품 재고
착각 포인트 : 주문 단위로 락을 걸면 충분하다 → 여러 주문이 같은 상품에 영향을 줄 수 있음

기존 문제 - 재고 불일치 문제
기존에 재고 차감을 Dirty Checking으로 확인했는데 Dirty Checking은 트랜잭션이 커밋된 후 적용이 될 수 있습니다. 위와 같이 트랜잭션이 커밋 되기 전이 락이 해제되어 다른 스레드에서 상품을 조회한 경우 차감되지 않은 상품의 재고를 확인하고 작업이 이루어져 재고 불일치 문제가 발생했습니다.

TransactionSynchronizationManager 사용
기존 문제을 해결한 내용이 TransactionSynchronizationManager를 사용한 내용입니다. TransactionSynchronizationManager는 커밋 후 락을 해제하여 재고 불일치 문제를 해결하였습니다.

트랜잭션 범위 문제
TransactionSynchronizationManager 사용한 방법의 문제점을 발견했는데 Lock 획득이 트랜잭션 내부에 있으면, Lock의 생명주기가 트랜잭션 경계에 종속되어야 하는데, Lock 해제는 트랜잭션 Commit 후에 이루어져야 하므로 생명주기를 일관되게 관리할 수 없다 이렇게 되면 데드락, 데이터 불일치 등의 문제가 발생할 수 있습니다. 그래서 Lock 획득 시점을 트랜잭션 전으로 하여 트랜잭션 범위를 확실하게 조정하여 구조적 해결했습니다
트랜잭션 범위 문제를 해결하는 와중에 Self-Invocation 문제가 발생했습니다.
OrderService 클래스 안에서 lockCreateOrder 메서드와 saveOrder를 사용
DirtyChecking 때문에 saveOrder가 @Transcational 필요
lockCreateOrder에서 saveOrder를 호출하기 때문에 프록시 호출이 되지 않아 트랜잭션 적용이 되지 않는 Self-Invocation 문제 발생
해결 방안 : 별도의 서비스 분리 시도
Lock에 관한 기능을 LockService 클래스로 이동
Order에 관한 기능을 OrderService 클래스에 정리
Self-Invocation 문제를 해결하기 위해 별도의 서비스로 분리 하는 중
해결 방안 : 퍼사드 패턴 적용
흐름
Client ↓ OrderFacade → (Concurrency Control Layer) ↓ LockService → 락 획득 / 해제 ↓ OrderService → 주문 도메인 비즈니스 처리