Continuous Challenge

[항해플러스 7기 백엔드] 6주차 - 주요 기능(포인트 충전/차감, 상품 재고 차감, 선착순 쿠폰 발급)에 대한 동시성 제어 방식 비교 본문

Study/항해플러스 7기

[항해플러스 7기 백엔드] 6주차 - 주요 기능(포인트 충전/차감, 상품 재고 차감, 선착순 쿠폰 발급)에 대한 동시성 제어 방식 비교

응굥 2025. 1. 21. 23:42
728x90
728x90

주요 기능 분석

1. 포인트 충전

  • 사용자가 자신의 계정에 포인트를 충전. 충전 금액이 포인트 잔액에 추가됨.
  • 단일 계정 기준으로 충전이 수행 되므로 충돌 가능성 낮음.
  • 충전 요청이 동시에 발생할 가능성은 상대적으로 낮으며, 발생하더라도 포인트는 독립적으로 누적 처리

2. 결제 시 포인트 차감

  • 사용자가 결제 시 포인트 차감. 잔액 부족 시 차감 불가.
  • 단일 계정 기준으로 차감이 수행되므로 충돌 가능성 낮음.

3. 주문 시 상품 재고 차감

  • 주문 시 선택한 상품의 재고를 차감. 재고 부족 시 차감 불가.
  • 동일 상품에 대한 다수의 주문 요청이 동시에 발생할 가능성 높음.

4. 선착순 쿠폰 발급

  • 쿠폰 발급 시 쿠폰의 잔여수량을 차감하고 사용자에게 쿠폰을 발급함.
  • 다수의 사용자가 동시에 요청하여 쿠폰 초과 발급 가능성이 매우 높음.

동시성 제어 방식 비교

1. 낙관적 락

  • 충돌 가능성이 낮다고 가정하고 작업을 진행. 커밋 시점에 충돌 여부 검증.
  • 데이터 갱신 후 버전 번호 비교로 충돌 검증.
  • 동시성 처리 효율적. Deadlock 방지.
  • 적합한 상황 : 충돌 가능성이 낮은 환경. 읽기 작업 비중이 높은 경우 -> 포인트 차감

2. 비관적 락

  • 충돌 가능성이 높다고 가정하고 작업 중에 잠금을 걸어 충돌 방지.
  • 데이터 접근 시 잠금을 설정하여 다른 트랜잭션의 접근 차단.
  • 데이터 무결성 보장. 충돌 방지로 롤백 비용 감소.
  • 동시성 처리 성능 저하. 잠금 유지로 자원 낭비 가능.
  • 적합한 상황 : 충돌 가능성이 높은 환경. 쓰기 작업 비중이 높은 경우 -> 상품 재고 차감, 선착순 쿠폰 발급

3. 분산 락

  • 분산 시스템에서 공유 자원에 대한 잠금 관리.
  • 중앙화된 락 서비스(Redis, Zookeeper 등)를 사용해 잠금 관리.
  • 분산 환경에서의 일관성 보장. 자원 접근 제어 가능.
  • 락 관리 시스템 필요. 네트워크 지연이 성능에 영향.
  • 적합한 상황 : 분산 시스템에서 자원 동시 접근 관리 필요 -> 상품 재고 차감, 선착순 쿠폰 발급

주요 기능별 락킹 방식 도입 및 비교

 주요 기능에 각 락킹 방식을 도입한 후 JMeter를 사용해 테스트 해보았다.

JMeter 지표

Summary Report
- Label : Sampler 명
- Samples : 샘플 실행 수 (Number of Threads X Ramp-up period)
- Average : 평균 걸린 시간 (ms)
- Min : 최소
- Max : 최대
- Std. Dev. : 표준편차
- Error % : 에러율
- Throughput : 초당 처리량 (bps) = JMeter에서는 시간 단위를 보통  TPS (Transaction Per Second)로 표현
- Received KB/sec : 초당 받은 데이터량 
- Sent KB/sec : 초당 보낸 데이터량
- Avg. Bytes : 서버로부터 받은 데이터 평균

1. 포인트 충전  (비관적 락)

포인트 충전은 충돌 가능성이 낮으며 충돌이 발생하더라도(같은 계정으로 동시에 충전 시도) 실패하지 않고 모든 요청이 성공해야 한다.

이 정도의 비교를 통해서도 비관적 락을 선택할 것 같다.

 

포인트 충전에 대해서는 동일한 유저가 동시에 5번(충돌 가능성 낮음을 가정)을 호출하는 케이스를 테스트하였다.

낙관적락

비관적락

 

5번 호출로 테스트해서인지 낙관적락 비관적락 결과의 차이는 크지 않았다. 

  • 낙관적락 - 충돌 시 예외 발생. 재시도 로직 구현 필요
  • 비관적락 - 대기 후 실행. 
  • 분산락 - 필요성에 비해 고비용.

하지만 낙관적 락의 경우 재시도 로직을 구현해야 하는 구현 복잡도가 발생하므로 비관적 락을 도입하기로 하였다.

2. 결제 시 포인트 차감 (비관적 락)

포인트 차감은 충돌 가능성이 낮으며 충돌이 발생할 경우(같은 계정으로 동시에 다른 주문에 대한 결제 시도) 꼭 성공하지 않아도 된다. (예외를 발생시켜 오류 메시지를 반환한다.)

 

포인트 차감에 대해서는 동일한 유저가 동시에 5번(충돌 가능성 낮음을 가정)을 호출하는 케이스를 테스트하였다.

낙관적락

비관적락

 

  • 낙관적락 - 충돌 시 예외 발생. 재시도 로직 불필요. 호출 건 일부 실패. 평균 소요시간 짧음.
  • 비관적락 - 모든 호출에 대해 성공.
  • 분산락 - 필요성에 비해 고비용.

결제의 경우에는 5번 호출임에도 Average, Max 에서 차이가 발생하는 것을 확인할 수 있었다. (낙관적 락 우세)

하지만 낙관적 락을 도입할 경우 에러가 60% 발생하는 반면, 비관적 락은 에러 0% 발생으로 모든 호출이 성공하는 것을 확인할 수 있다.

충돌 가능성이 낮기 때문에 실패가 발생하는 것보다는 모든 호출이 성공하는 것(모든 결제 건 성공)이 더 안정적인 서비스를 제공한다고 판단하여 비관적 락을 도입하기로 하였다.

3. 주문 시 상품 재고 차감 (비관적 락)

재고가 30개인 경우 100명의 서로 다른 유저가 동시에 결제(재고 차감) API를 호출하는 케이스를 테스트하였다.

낙관적락

Thread 100 일 때
Thread 1000 일 때

비관적락

Thread 100 일 때
Thread 1000 일 때

분산락

Thread 100 일 때
Thread 1000 일 때

모수가 작았을 땐(스레드 100개) 성능이 비관적 락 > 분산락 > 낙관적 락처럼 보였는데, 모수를 늘리니(스레드 1000개) 낙관적 락  >= 비관적 락 > 분산 락 순으로 보여졌다. (평균 소요시간 기준으로 비교)

  • 낙관적락 - 충돌 시 예외 발생. 재시도로직 불필요(에러메시지 리턴). 최초의 커밋
  • 비관적락 - 낙관적 락에 비해 소요 시간 긺.
  • 분산락 - 필요성에 비해 고비용. 소요 시간 가장 긺.

상품 재고 차감의 경우, 모든 호출이 성공할 필요가 없고 상품의 재고만큼만 성공하면 되므로 낙관적 락을 사용하기로 하였다.

 

낙관적 락 구현 후 동시성 테스트 중... "5개 재고를 가진 상품에 대해 10명이 주문할 경우 5건은 성공 5건은 실패한다" 동시성 테스트가 실패하는 현상이 발생했다.. (5건 성공을 기대했지만 1건만 성공..) 원인을 확인해보니 낙관적 락은 메커니즘상 최초의 커밋만 인정하기 때문에 최초 요청한 한명만 주문이 가능하게 된다. 

 

따라서 비관적 락을 사용하는 것으로 변경한다...

 

4. 선착순 쿠폰 발급 (분산 락)

쿠폰의 잔여 수량이 30개인 경우 100명의 서로 다른 유저가 동시에 쿠폰 발급 API를 호출하는 케이스를 테스트하였다.

낙관적락

Thread 100개 일 때
Thread 1000개 일 때

비관적락

Thread 100개 일 때
Thread 1000개 일 때

분산락

Thread 100개 일 때
Thread 1000개 일 때

선착순 쿠폰 발급 역시 모수가 작았을 땐(스레드 100개) 성능이 비관적 락 > 분산락 > 낙관적 락처럼 보였는데, 모수를 늘리니(스레드 1000개) 낙관적 락  >= 비관적 락 > 분산 락 순으로 보여졌다. (평균 소요시간 기준으로 비교)

  • 낙관적락 - 충돌 시 예외 발생. 재시도 로직 불필요(에러메시지 리턴).
  • 비관적락 - 낙관적 락에 비해 소요 시간 긺.
  • 분산락 - 소요 시간 가장 긺. 더 많은 트래픽 발생 시 우세할 수 있음..(?)

하지만 트래픽이 대량으로 들어올 수 있는 확률이 가장 높은 기능이므로 분산 락을 사용하기로 하였다. (적용 경험도 해볼 겸..)


후기

1000개의 모수는 실제 환경에 비해 충분하지 않은 모수일 수 있지만, 로컬 환경에서 테스트한 결과 분산락의 성능이 비관적 락이나 낙관적 락에 비해 우세하지 않을 수도 있다는 것을 알았다. 이론으로 생각했을 때와 실제 도입하여 테스트한 결과가 다를 수 있다는 것을 알았고, 도입 테스트 과정이 필요하다는 것을 느꼈다.

728x90
728x90
Comments