내가 만든 이커머스 서비스는 재고 차감과 쿠폰 사용 과정에서 동시성 문제에 취약했다. 실제로 10개의 동시 요청을 발생시키는 테스트를 진행한 결과, 재고가 정상적으로 차감되지 않는 문제가 발생했다.
문제를 분석한 결과 여러 요청이 동일한 재고 데이터를 동시에 읽고 수정하면서 정합성이 깨지고 있었고, 이를 해결하기 위해 원자적 연산(Atomic Update) 방식을 적용했다. 적용 이후에는 동일한 테스트 환경에서도 재고가 정상적으로 차감되는 것을 확인할 수 있었다.
하지만 구현을 진행하면서 단순히 동시성 문제를 해결하는 것보다 더 중요한 고민들이 생겼다. 어떤 자원을 보호해야 하는지, 도메인 규칙을 어떻게 유지할 것인지, 그리고 모든 문제를 원자적 연산으로 해결하는 것이 과연 올바른 선택인지에 대한 고민이었다.
이번 글에서는 동시성 문제를 해결하는 과정에서 경험한 시행착오와 고민, 그리고 그 과정에서 얻은 인사이트를 정리해보려고 한다.
보호받아야 하는 자원
아직 실무에서 동시성 문제가 발생할 정도의 높은 트래픽을 경험해 보지는 못했지만, JPA의 더티체킹을 통해 이루어지는 대부분의 데이터 변경은 동시성 문제에 노출될 수 있다. 그렇다면 모든 변경 데이터를 보호해야 하는 것일까?
중요한 것은 동시성 문제가 발생할 수 있는지 여부보다, 해당 자원이 비즈니스에 얼마나 큰 영향을 주는지인 것 같다.
예를 들어 쇼핑몰의 재고는 1개만 잘못 차감되어도 실제 매출과 직결될 수 있다. 쿠폰이나 포인트 역시 마찬가지다. 반면 사용자가 자신의 휴대폰 번호를 변경하는 기능은 동시 수정이 발생할 가능성도 낮고, 설령 발생하더라도 비즈니스적으로 큰 손실이 발생하지 않는다.
결국 모든 데이터를 동일한 수준으로 보호하려고 하기보다는, 비즈니스적으로 반드시 정합성을 보장해야 하는 자원이 무엇인지 먼저 식별하는 것이 중요하다고 느꼈다.
동시성 제어는 기술적인 문제가 아니라 비즈니스 요구사항에서 출발해야 한다는 점이 중요하다.
도메인 규칙을 살리면서 해결하기
동시성 문제를 해결하는 방법에는 비관적 락, 낙관적 락, 원자적 연산 등 다양한 선택지가 존재한다. 각각의 방식은 장단점이 있기 때문에 상황에 따라 적절한 방법을 선택해야 한다.
이번 구현에서는 재고 차감에 원자적 연산 방식을 적용했다. 하나의 SQL로 재고를 안전하게 차감할 수 있어 성능과 정합성 측면에서 효과적인 방법이라고 판단했다.
하지만 원자적 연산을 적용하면서 새로운 고민도 생겼다. 기존에는 도메인 객체가 재고 차감 규칙을 직접 관리했지만, 원자적 연산을 사용하면 일부 비즈니스 규칙이 SQL로 이동하게 된다. 이 경우 애플리케이션 입장에서는 단순히 성공 또는 실패 여부만 확인할 수 있기 때문에, 어떤 조건 때문에 실패했는지 세밀하게 구분하기 어려워진다.
만약 사용자에게 보다 정확한 실패 사유를 전달해야 한다면 추가적인 검증 과정이 필요할 수 있다. 예를 들어 재고 차감 전후에 필요한 정보를 조회하거나, 사전 검증과 사후 검증을 통해 도메인 관점의 규칙을 보완할 수 있다.
동시성 문제를 해결하는 것 자체도 중요하지만, 도메인 규칙과 응답의 정확성을 어떻게 유지할 것인지 역시 함께 고민해야 한다.
동시성 문제는 다른 락이 해결해 줄 수 있다.
주문을 취소할 때는 사용한 쿠폰을 다시 사용할 수 있는 상태로 변경하고, 주문 과정에서 차감했던 재고도 함께 복구해야 한다.
그렇다면 다음과 같은 흐름이 있다고 가정해 보자.
주문 취소
├─ 재고 복구
└─ 쿠폰 원복
처음에는 재고 복구와 쿠폰 원복에도 각각 동시성 제어가 필요하다고 생각했다. 하지만 조금 더 생각해 보니 더 중요한 것은 주문 취소 자체였다.
예를 들어 사용자가 주문 취소 버튼을 연속으로 클릭하여 동일한 주문에 대해 취소 요청이 두 번 들어온다고 가정해 보자. 주문 취소 상태 변경이 동시성에 취약하다면 재고는 실제 주문 수량보다 더 많이 복구될 수 있고, 쿠폰 역시 여러 번 원복 될 수 있다.
반대로 주문 취소 자체를 한 번만 수행되도록 보장한다면 그 이후에 실행되는 재고 복구와 쿠폰 원복은 중복 실행될 일이 없다.
모든 하위 작업에 개별적으로 동시성 제어를 적용하기보다는, 어떤 자원을 기준으로 정합성을 보장해야 하는지 먼저 판단하는 것이 중요하고 상위 도메인의 상태 변경을 안전하게 만드는 것만으로도 여러 동시성 문제를 함께 해결할 수 있다.
하지만 재고 차감에서는 하나의 상품에서 일어날 수 있는 상황으로 주문 취소와 별개로 동시성을 챙겨줘야 한다.
느낀 점
단순히 기술을 적용하는 것보다 어떤 자원을 보호해야 하는지 먼저 판단하는 것이 중요하다는 점을 느꼈다.
처음에는 재고 차감과 쿠폰 사용에만 집중했지만, 주문 취소와 같은 다른 흐름에서도 동일한 문제가 발생할 수 있다는 것을 알게 되었다. 결국 동시성 문제는 특정 API의 문제가 아니라 하나의 자원을 변경하는 모든 경로를 함께 고려해야 하는 문제였다.
또한 원자적 업데이트를 적용하면서 정합성을 확보할 수 있었지만, 그 과정에서 도메인 규칙이 데이터베이스로 이동하는 트레이드오프도 경험하면서 동시성 문제에는 정답이 있는 것이 아니라 비즈니스 요구사항과 도메인 특성에 따라 적절한 전략을 선택하는 것이 중요하다.
'루퍼스 4기' 카테고리의 다른 글
| 주문 상태는 Pending 인데, 사용자는 결제 안내를 받았다. (0) | 2026.06.26 |
|---|---|
| 인덱스 적용부터 반정규화까지, 조회 성능 개선 실험 (0) | 2026.06.19 |
| 쇼핑몰에서 안전하게 재고 차감하기 (0) | 2026.06.12 |
| 주문 생성 흐름에서 책임을 어디까지 나눌 것인가 (0) | 2026.05.29 |
| 루퍼스 2주차 WIL (소프트웨어 설계) (0) | 2026.05.24 |