동시성 문제 - 비즈니스 애플리케이션 (Part 3)

web | 07 March 2020

Tags | concurrency architecture enterprise database jpa

시리즈

Part 1 : 동시성 문제 - 일반론

Part 2 : 동시성 문제 - 데이터베이스와 JPA

Part 3 : 동시성 문제 - 비즈니스 애플리케이션 (NOW)

앞선 글들에서 소개한 동시성 문제에 관련한 일반론을 비즈니스 애플리케이션의 개발에서도 이용할 수 있습니다.

일반론 파트에서 다루었다시피, 비즈니스 애플리케이션에서의 동시성 문제도 읽고→쓰는 프로세스에서 발생합니다.

쇼핑몰 시스템에서의 주문과 재고 차감

우리가 물건을 파는 쇼핑몰 커머스 시스템을 개발하고 있다고 생각해봅니다. 그 중에서도 고객이 물건을 주문하는 과정의 비즈니스 로직을 개발하고 있습니다. 고객의 주문을 처리하는 과정을 도식화해보겠습니다.

img-name

  1. 재고 남았는지 확인
  2. 가격을 계산하고 → 주문서를 작성하고 → 결제를 요청
  3. 재고를 차감

이 때, 하나의 상품에 동시에 여러 주문이 들어올 때 발생할 수 있는 동시성 문제는 무엇일까요? 네, 바로 “손실되는 업데이트” 입니다. 아래의 그림을 살펴보시죠.

(참고로 한번 읽어온 데이터는 메모리에 저장하는 애플리케이션의 특성을 가정합니다. 또다른 동시성 문제인 “일관성 없는 읽기”의 문제는 따라서 논외가 됩니다.)

img-name

고객이 주문을 한 시점에는 재고가 2개였지만, 주문을 처리하는 과정 중 먼저 누군가 2개를 모두 주문해가면서 재고를 2 차감했습니다. 따라서 재고는 0이 되었죠. 하지만 현재 이 고객의 주문 세션에서는 주문이 모두 끝난 뒤 재고를 2에서 1로 업데이트하기 때문에, 손실되는 업데이트가 발생하게 됩니다. 재고는 더이상 믿을 수 없는 값이 됩니다.

낙관적 잠금을 통한 해결

img-name

동시성 제어 기법 중 하나인 낙관적 잠금 기법을 이용해 아키텍처를 구성하면 믿을 수 없는 재고값 문제를 해결할 수 있습니다. 하지만 전체 프로세스 중에 Transaction 의 원자성을 보장하지 못하게 하는 “외부 시스템 연동” 같은 과정이 있다면, 낙관적 잠금은 사용하기 어렵습니다. 낙관적 잠금은 전체 프로세스의 실패를 마지막 저장 시도 시점에 알 수 있는데, 원자적으로 Rollback 이 어려운 프로세스라면 전체 시스템의 정합성이 깨지기 때문입니다.

(마지막에 재고 차감에서 충돌이 일어나 주문은 실패하였는데, 외부 결제 시스템 호출은 이미 일어나서 고객의 돈이 빠져 나간다면 시스템의 큰 문제일 것입니다.)

이런 경우, 시스템의 활동성을 조금 포기하더라도 정확성을 높일 수 있는 “비관적 잠금” 을 사용할 수 있습니다.

비관적 잠금을 통한 해결

img-name

비지니스 어플리케이션에서의 비관적 잠금은 보통 동일한 작업을 하는 프로세스들 중 단 하나만이 점유할 수 있는 리소스 (Lock 혹은 Semaphore) 를 이용해 구현합니다. 한 프로세스가 재고 차감에 대한 Lock 을 점유하고 있다면, 현재 주문하려는 고객은 그 Lock 점유가 해제될 때까지 기다렸다가 프로세스를 실행합니다. 모든 주문 프로세스가 순차적으로 진행되는 것이죠.

비관적 잠금은 낙관적 잠금에 비해 활동성은 줄어들어 주문이 몰리는 시점에 고객은 더 느린 주문을 경험하겠지만, 결제만 되고 취소가 되는 등의 부정확한 시스템을 경험하게 될 확률은 그만큼 줄 것입니다.