락 생성 시 트러블 슈팅

이동근·2025년 12월 3일

custom-shopping

목록 보기
3/3
post-thumbnail

상황

락의 필요성에 대한 내용

  • 동시성 테스트로 인해 락의 필요성을 알았고 낙관락, 비관락, 분산락 중에 쇼핑몰이라는 프로젝트 특징을 이용하여 분산락을 걸어 주요 기능을 수행하는 관점에서 생긴 트러블입니다.

과정

동시성 문제 잘못된 해결 사례

기존 팀 프로젝트에서 주문생성과, 주문 취소 시 어느 정도 동시성 문제를 해결했다고 생각하였으나 재시도 과정에서 여러 의문점들이 생기고 복잡한 구조에 대한 의문이 생겨 추가적인 리팩토링 작업을 진행했습니다.

위의 링크는 이전에 기록해 놓은 팀 프로젝트에 내용과 트러블슈팅이 적혀 있습니다. 링크 안에 있는 내용이 완전히 틀린 내용만 존재하는 것이 아니며 프로젝트 진행 과정이라고 봐주시면 좋을 거 같습니다.


수정 부분

주문생성과 주문취소 락 대상 차이

동시성 문제 잘못된 해결 사례 내용에는 주문생성과 주문 취소 시 락 대상의 차이에 대한 내용이 나오는데 주문 생성 시에는 상품에 락을 걸고 주문 취소 시에는 주문자체에 락을 거는 방법을 사용했습니다.

주문 취소할 때는 이미 주문이 생성되어 있으니 주문 정보에 상품과 수량이 다 기록되어 있다고 생각하였습니다.

주문 취소 시 주문 하나만 확인하면 다른 주문들과 충돌이 발생하지 않을거라는 논리적 추론에서 착각이 생겼습니다.

하지만 실제 공유 자원은 상품 재고입니다.

동일 상품이 여러 주문에 포함되어 있고 동시에 여러 주문 취소가 발생하면 같은 상품 재고를 수정해야하는 상황이 발생합니다

따라서 주문 취소 시에도 주문에 락을 거는 것이 아닌 상품에 락을 걸어야합니다.

  • 결론 : 주문 생성, 주문 취소 시 락 대상 → 상품 재고

  • 착각 포인트 : 주문 단위로 락을 걸면 충분하다 → 여러 주문이 같은 상품에 영향을 줄 수 있음


트랜잭션 범위 문제

기존 문제 - 재고 불일치 문제

기존에 재고 차감을 Dirty Checking으로 확인했는데 Dirty Checking은 트랜잭션이 커밋된 후 적용이 될 수 있습니다. 

위와 같이 트랜잭션이 커밋 되기 전이 락이 해제되어 다른 스레드에서 상품을 조회한 경우 차감되지 않은 상품의 재고를 확인하고 작업이 이루어져 재고 불일치 문제가 발생했습니다. 

TransactionSynchronizationManager 사용

기존 문제을 해결한 내용이 TransactionSynchronizationManager를 사용한 내용입니다.

TransactionSynchronizationManager는 커밋 후 락을 해제하여 재고 불일치 문제를 해결하였습니다.

트랜잭션 범위 문제

TransactionSynchronizationManager 사용한 방법의 문제점을 발견했는데 

Lock 획득이 트랜잭션 내부에 있으면, Lock의 생명주기가 트랜잭션 경계에 종속되어야 하는데, Lock 해제는 트랜잭션 Commit 후에 이루어져야 하므로 생명주기를 일관되게 관리할 수 없다

이렇게 되면 데드락, 데이터 불일치 등의 문제가 발생할 수 있습니다.

그래서 Lock 획득 시점을 트랜잭션 전으로 하여 트랜잭션 범위를 확실하게 조정하여 구조적 해결했습니다

Self-Invocation

  • 트랜잭션 범위 문제를 해결하는 와중에 Self-Invocation 문제가 발생했습니다.

  • OrderService 클래스 안에서 lockCreateOrder 메서드와 saveOrder를 사용

  • DirtyChecking 때문에 saveOrder가 @Transcational 필요

  • lockCreateOrder에서 saveOrder를 호출하기 때문에 프록시 호출이 되지 않아 트랜잭션 적용이 되지 않는 Self-Invocation 문제 발생

  • 해결 방안 : 별도의 서비스 분리 시도

    • Lock에 관한 기능을 LockService 클래스로 이동

    • Order에 관한 기능을 OrderService 클래스에 정리


퍼사드 패턴

  • Self-Invocation 문제를 해결하기 위해 별도의 서비스로 분리 하는 중

    • LockService, OrderService 역할과 책임이 명확히 구분되지 않아 락 제어 로직과 비즈니스 로직이 섞여 코드 가독성과 유지보수가 어려워지는 문제 발생
  • 해결 방안 : 퍼사드 패턴 적용

    • OrderFacade 도입 : 단일 진입점이 되어 LockService와 OrderService의 흐름을 조율하도록 변경하였습니다.

흐름

Client
  ↓
OrderFacade  → (Concurrency Control Layer)
  ↓
LockService  → 락 획득 / 해제
  ↓
OrderService → 주문 도메인 비즈니스 처리
profile
안녕하세요

0개의 댓글