재고 예약 방식조건부 Update캐싱
재고 관리 문제의 경우, 일반적인 락 방식만으로는 운영상 한계가 있다고 생각했습니다.
| 해결방안 | 방식 | 단점 |
|---|---|---|
| 비관적 락 | DB에서 락을 얻을 때까지 대기 후, 조작 가능 | 1. DB 커넥션 풀 상황에 따라 실패 가능 |
2. 락 대기시간에 따라 응답 지연 |
| 낙관적 락 | 조회 시점과 조작 시점의 데이터가 동일한 경우에만, 조작 가능 | 1. 요청 순서대로 처리 불가 2. 재시도 과정에 따라 응답 지연 or 실패 가능 |
동일한 재고에 차감 요청이 초당 100개 들어온다면?
(재고 차감 트랜잭션은 10ms동안 실행된다고 가정)
락 방식의 단점들을 상쇄하는 방안을 생각해봤습니다.
| 해결방안 | 단점 | 상쇄방안 |
|---|---|---|
| 비관적 락 | 1. DB 커넥션 풀 상황에 따라 실패 가능 |
2. 락 대기시간에 따라 응답 지연 | 락으로 묶이는 트랜잭션 범위를 최소화
or DB 락을 안 쓰면 베스트 | | 낙관적 락 | 1. 요청 순서대로 처리 불가 2. 재시도 과정에 따라 응답 지연 or 실패 가능 | DB 접근 전에 요청을 직렬화 → 순서 보장 + 공유자원 충돌 제거 |
직면한 현실 상황에 적용해보면, 비즈니스 요구사항은 락이 아닌 “세마포어”에 가깝습니다.
따라서 각 요청의 재고 점유 상황을 기록하는 것으로 무거운 트랜잭션에서 벗어날 수 있겠다고 생각했습니다.
| 단계 | 실제 재고 | 점유 중인 재고 | 주문 가능 재고 | 비고 |
|---|---|---|---|---|
| 초기 | 3 | 0 | 3 | — |
| 요청1이 1개 주문 | 3 | 1 | 2 | 요청1: 예약 중 |
| 요청2가 2개 주문 | 3 | 3 | 0 | 요청2: 예약 중 |
| 다른 요청이 주문 시도 | 3 | 3 | 0 | **주문 가능 재고 0 |
| → 실패** | ||||
| 요청1 결제 실패 / 요청2 결제 성공 | 1 | 0 | 1 | 요청1: 예약 해제, |
| 요청2: 차감 확정 |
또한, 재고의 개수가 핵심인 만큼, 낙관적 락과 비슷한 개념인 조건부 Update로 데이터 조작의 원자성을 보장할 수 있다고 생각했습니다.
점유하는 순간에 요청 수량만큼 재고가 존재하면, 점유 가능함
→ 조회 시점의 재고와 같은지는 상관 X
DB에 재고 점유 상황을 명시적으로 기록하면, 무결성 보장하는 락을 흉내낼 수 있음
결제 전에 미리 확보하기 위해, 주문 시에 재고를 예약합니다.
→ 결제 전 재고 예약, 결제 후 재고 차감
재고 복구는 결제 취소가 확인되면 처리합니다.
특히 재고 예약 방식을 도입하면서, 다른 고민사항들도 자연스럽게 해결할 수 있었습니다.
인기/특가 상품은 일정 시간에 동시 요청이 집중되는 상황이 많습니다.
재고 예약 방식과 조건부 Update로 정합성을 보장한다고 해도, DB 과부하가 발생하면 정상적인 서비스 제공 자체가 불가능합니다.
같은 데이터 조작을 위해 대기하느라, DB 커넥션 오래 점유
→ DB Connection Pool 고갈
요청 스레드도 DB 응답을 기다리느라 오래 점유됨
→ Thread Pool 고갈
DB 서버의 CPU, 메모리, 디스크 사용률이 증가
→ 쿼리 실행도 더 오래 걸림
DB가 감당할 수 있는 만큼, DB 요청을 줄이는 것부터 고려했습니다.