[Java/Spring]간단한 E-Commerce 프로젝트를 통해 알아보는 동시성 제어.

nana·2024년 10월 27일
0

Spring

목록 보기
7/9

0. 동시성 제어는 참 익숙한 용어이다.

일전에 동시성 제어에 대해 글을 쓴 적이 있다.

🔽동시성 제어에 관한 포스팅

(두 번이나 썼는데 다시 들여다 보지 않으니 잘 기억이 나지 않는다😶‍🌫️)

다시 훑어보는 동시성문제.

동시성 제어 목적

  • 여러 사용자가 DB에 접근하더라도 데이터의 일관성을 보장하고 데이터의 무결성을 유지.
  • 위를 만족하며 데이터베이스 시스템의 성능과 효율성을 보장하는 것.
    1. 분실된 갱신(Lost Update)
      두개의 트랜잭션이 같은 데이터를 갱신하는 작업을 진행하게 되면서 하나의 작업이 진행되지 않는 경우
    2. 모순성(Inconsistency)
      두개의 트랜잭션이 같은 데이터를 동시에 갱신하게되어 사용자가 원하는 결과와 일치하지 않은 상태가 되는 경우
    3. 연쇄복귀(Cascading Rollback)
      두개의 트랜잭션이 같은 데이터를 갱신하는 작업을 진행하는 과정에서 하나의 트랜잭션이 실패하면 원자성에 의해 두 트랜잭션 모두 복귀하는 경우
    4. 비완료 의존성(Uncommitted Dependency)
      한개의 트랜잭션이 실패하였을때, 이 트랜재션이 회복하기전에 다른 트랜잭션이 실패한 수행 결과를 참조하는 경우

Race Condition

  • 여러 작업(Process, Thread, Transaction...)이 공통 자원에 대해 동시에 접근할 때 발생하는 문제.
  • 흔히 Database의 동일한 데이터에 동시에 여러 작업이 수행될 때 의도하지 않은 작업 결과가 발생.

ex1) 데이터 수정작업이 다량의 요청에 의해 자주 변경 되는 경우
ex2) 다수의 작업이 데이터 수정에 관여해 자주 변경되는 경우

💡 DB 와 연관지어 보는 동시성 이슈

  • 상호 배제(Mutual Exclusion)
    한번에 하나의 작업만 특정 데이터에 접근할 수 있도록 막는 방식
    (다른 프로세스의 진입을 금지)
    우리가 흔히 아는 Database Lock 을 이용해 달성
  • 교착 상태(Deadlock)
    서로 다른 작업이 각각의 블로킹 상태로 진입하여 다른 자원의 블로킹에 진입할 수 없어 기다리고 있는 상태(잔여좌석과 좌석점유를 보면?)
    e.g. Process 1와 Process 2 는 모두 Resource1, Resource2 데이터를 사용해야 한다.
    (1) Process 1 가 Resource1 에, Process 2 가 Resource2 에 Lock
    (2-1) Process 1 는 Process 2 가 잠근 Resource2 가 풀릴 때까지 대기
    (2-1) Process 2 는 Process 1 가 잠근 Resource1 이 풀릴 때까지 대기
    (3) 둘은 Connection Timeout 까지 Lock 을 해제하지 못함.

해결방식

Distributed Lock (분산 락)

  • 분산된 서버/클러스터간에도 Lock을 보장하는 것.

Kafka Messaging

  • 메세지 큐와 같이 순서 보장이 가능한 장치를 이용해 동시성 이슈를 해결하는 방식.

내가 실무에서 경험한 도메인에서는 동시성 제어를 해 줄만큼 서비스가 크지않고, 크다 하더라도 동시 접근에 대한 제어를 해 줄 필요가 없었다.
때문에 동시성 제어라는 용어도 처음 알게되었고 배우면 배울 수록 중요한 기술이라는 것을 알게되었다.

백마디 말 보다 한번 실천하는게 낫다고 백번 용어 읽는 것 보다 실제 간단한 예제 프로그램에 적용해 보는 것이 머리속에 아주 깊게 남을 것이다.

1. 🛒e-commerce에 적용할 수 있는 동시성 제어.

이커머스 프로젝트의 기능

나의 이커머스 프로젝트(aka. OhSir39cm)는 다음과 같은 기능을 가지고 있다.

  • 포인트 충전/사용
  • 상품 주문 /결제
  • 상품 조회
  • 장바구니 추가 / 삭제
  • 최근3일의 판매 순위 조회

내가 떠올릴 수 있는 동시성 제어.

그렇다면, 이 서비스에서 발생할 수 있는 동시성문제는 어떤 것들이 있을까?

  • 한 명의 포인트 충전, 또는 사용이 동시에 들어올 경우
    ▶ 같은 고객의 포인트 사용 또는 충전은 차례대로 수행되어야 하며, 각 요청은 최신의 포인트 정보를 가지고 있어야 한다.
  • 한 물품에 대한 사용이 동시에 들어올 경우
    ▶ 한 물품에 대한 구매 요청이 동시에 들어올 경우 각 주문은 최신의 재고 정보를 가지고 있어야 한다.
  • 장바구니에 담으려는 물품이 품절인 상품인지
    ▶ 한 물품에 대해 장바구니에 담는 요청을 여러명이서 할 때 재고에 대해 최신으로 확인이 되어야한다.
  • 실시간 랭킹을 조회할 때

포인트 충전에 대한 요청은 서버가 렉이 걸려서 여러번 요청이 전송되는 경우가 있겠지만 사용자 입장에서는 한번도 느껴본 적 없는 오류였다. (아마 내가 충전 요청을 여러번 한 적이 없거나 서버에서 제어를 잘 해줘서 그런거였겠지?)

그리고 구매와 물품에 대한 실시간 재고 반영은 한정 물품 구매를 한 번이라도 해본 사람은 넘나 뼈아프게 느껴본 경험해 본 적 있을것이다.. (내 느린 손을 탓해야지....)

From. GPT

내가 떠올린 것과 차이가 있을지 궁금해서 AI선생님께 여쭈어봤다.

3번은 내가 생각한 것과 조금 다른 부분인 것 같다.

나의 관점은
: 한 상품에 대해 여러명이 장바구니에 동시에 담으려고 하는 경우(약간 구매와 비슷) 이고

지피티선생의 관점은
: 한 고객의 장바구니 내의 한 상품에 대한 추가 삭제 문제 이다.

그리고 판매 순위 조회에 대한 동시성 문제도 있을 수 있다는건 생각하지 못한 부분이어서 신선했다.
이건 심화 주제로 구현해봐야겠다.

2. 어떤 방법으로 동시성 제어를 해 주어야 할까?

DB Lock을 이용한 방식

DB의 데이터에 대해 동시에 접근하는 것을 제어하는 방식으로 S-Lock(공유락), X-Lock(배타락)이 있고
Lock을 이용해 동시성을 제어하는 방식은 Optimistic Lock(낙관적 락)Pessimistic Lock(비관적 락)이 있다.


기존에 구현한 소스에는 비관적 락 + 배타락(select for update)을 선택하여 진행하였다.


이렇게 하면 하나의 주문에 대해 여러 요청을 보냈을 때 재고 개수만큼만 성공하는 동시성 통합테스트를 진행할 수 있다.

Pessimistic Lock

비관적 락은 자주 변경되는 데이터에 대한 동시성 제어를 위해 사용된다.
이 프로젝트에서 자주 변경되는 데이터라면?
주문 & 포인트 충전이 될 것이다.

Optimistic Lock

낙관적 락은 읽기작업이 많은 경우 @Version관리를 활용해 충돌을 감지할 수 있다.
주로 읽기작업인 랭킹 기능을 낙관적 락으로 구현해보면 어떨까?

Redis를 이용한 방식

Redis는

[장점]

  • 빠른 응답속도
  • SETNX 또는 Redis 분산 락(Lua 스크립트를 활용한 분산 락)을 통해 간단하게 락을 구현가능.
  • 원자적 연산

위와 같은 장점을 가지고 있어 빠른 속도와 원자적 연산을 제공하기 때문에 실시간으로 업데이트되는 데이터의 동시성 문제를 해결하는데 유용하다.

적합한 시나리오

  • 포인트 충전과 사용 : Redis로 포인트 잔액을 원자적으로 업데이트 할 수 있고, 세션을 짧게 유지하는 방법으로 빠른 처리가 가능하다.
  • 재고 차감 : 인벤토리 카운트를 Redis에서 관리, 락을 걸어 사용하면 동시성 이슈를 방지할 수 있다.

[한계점]

  • 데이터 영속성 : Redis가 인메모리 저장소 이기 때문에 영속성이 필요하면 백업 설정이 필요하다.
  • 복잡한 상태관리 보다는 단일 작업의 동시성 제어에 적합하다.

kafka를 이용한 방식

Kafka는

[장점]

  • 비동기 이벤트 처리
  • 데이터 로그 영속성
  • 확장성

적합한 시나리오

  • 주문 생성과 결제 : 결제 과정에서 여러 서비스간의 통합이 필요할 경우 Kafka의 이벤트 스트리밍이 유리하다.
  • 실시간 순위집계 : 판매 순위를 이벤트로 발행하면 비동기로 집계하는데 적합하다.

[한계점]

  • 복잡한 설정과 운영 : 설치와 운영이 복잡할 수 있고, 초기 설정에 시간이 필요하다.
  • 비동기 처리로 인한 지연 가능성 : 비동기 처리 특성상 데이터가 실시간으로 반영되지 않는 경우가 있다.

3. 결론..?

각각의 장단점이 있어 우선은
주문 생성은 kafka로, 재고 차감과 포인트 충전은 Redis로 구현해 보고자 한다.

또는 Redis와 kafka를 모두 사용하는 혼합 접근 방식도 고려해 볼 것이다.
이후 두 가지 방법을 모두 구현해 본 후 성능(속도)을 비교할 예정이다.

profile
BackEnd Developer, 기록의 힘을 믿습니다.

0개의 댓글