-
Notifications
You must be signed in to change notification settings - Fork 0
김현수 9주차 개인 회고
Kim Hyunsu edited this page Aug 25, 2024
·
3 revisions
목록: 김현수
특이사항: pair coding with @june-777

낙관적 Lock 고민 배경
- 비관적 Lock을 사용하는 경우, 주문 트래픽이 몰릴 경우 DB 부하가 심해질 수 있음
- 낙관적 Lock을 사용하여, DB 부하를 애플리케이션 레벨로 분산시키자.
비동기 스레드 고민 배경
-
낙관적 Lock을 사용 시, 동시성 이슈에 대해 재처리 로직을 수행해야 한다.
-
주문 3회 재시도 등 재처리 로직을 수행하면, 해당 응답을 수행하는 만큼 톰캣 커넥션 풀을 물고 있어야 함
-
이를 비동기 워커 스레드로 분리하여 해결할 수 있지 않을까
- 클라이언트가 요청이 처리가 되었는지 알려면 결국 서버와 연결이 되어 있거나 요청을 계속 보내야하기 때문에 결국 서버 부하가 생긴다.
- 결제 완료/실패 알림은 1-2분 뒤에 와도 된다고 가정하고 풀링 간격을 1분으로 해 요청 빈도를 낮춘다.
-
동적으로 전략을 바꾸는 방법도 고려해보는 것도 좋겠다.
-
- 비동기로 처리하는 서비스, 2)동기로 처리하는 서비스로 나누어서 구현하고 각각의 서비스가 효율적인 TPS 지점을 확인해보자.
-
트래픽을 기준으로 요청을 분기하는 방식
-
결제 API
- 낙관적 락을 사용하다가 재고가 임계치 이하로 줄어들면 비관적 락을 적용하는 방법
단일 서버에서는 쉽게 해결될 수 있음/ 하지만 분산 서버일 때도 고려는 해보자
- 일반적인 상황에서는 낙관적 락 사용, 재고가 임계치 이하로 떨어지면 비관적 락으로 전환
방안1. 서버 로드 시점에, 모든 메뉴의 재고 수량을 웜업
단점: 불필요한 Redis 메모리 사용률 증가
- 750만건의 메뉴에 대한 재고 수량을 Redis에 웜업하면, 불필요하게 Redis 메모리 사용량이 증가함
방안2. 서버 로드 시점에, 인기 가게 메뉴의 재고 수량만 웜업
단점: 성능 테스트가 어려움, 서비스 운영 시점에 고려해볼만 함
방안3. 카트에 메뉴를 넣는 시점에 재고 수량을 캐싱
- Redis에 메뉴의 재고가 캐싱되어 있으면, 캐싱된 데이터는 최신정보이므로, 캐싱받은 재고로 장바구니에 메뉴를 추가한다.
- Redis에 메뉴의 재고가 캐싱되어 있지 않으면, 메뉴 ID에 분산락을 걸고 메뉴의 재고를 캐싱한다.
- 분산락을 거는 이유: 주문하기 API에서 Redis에 재고를 최신화하는 과정에서, 재고 정합성 불일치가 발생할 수 있기 때문
- 분산락이 이미 걸려있으면, 메뉴의 재고가 캐싱되어 있는지 다시 확인한다.
- 분산락이 걸려있다는 것은 카트담기API 혹은 주문하기API에서 메뉴 재고를 최신화하고 있다는 의미이므로.
![]() |
![]() |
![]() |
---|---|---|
시스템 설계 | 트랜잭션을 반드시 걸어야하는 이유 | 재고 접근 시나리오 |
sequenceDiagram
participant C as 클라이언트
participant CS as 장바구니서비스
participant CR as 장바구니저장소
participant MR as 메뉴저장소
participant R as 레디스
participant DB as 관계형DB
participant LS as 잠금서비스
C->>CS: addMenu(AddCartCommand)
CS->>CR: getCart(customerId)
CR-->>CS: 고객 장바구니 반환
CS->>DB: findByIdWithStore(menuId)
alt 메뉴 찾음
DB-->>CS: 메뉴 반환
loop 메뉴ID 확인 및 저장
CS->>R: 메뉴ID 존재 여부 확인
alt {메뉴ID:재고}가 레디스에 존재
R-->>CS: 존재 확인 + RDB는 과거 정보이고 Redis 는 최신 정보이기 때문에 RDB에서 반환받은 재고를 Redis 재고로 업데이트
else {메뉴ID:재고}가 레디스에 없음
CS->>LS: 락 확인(menuId)
alt 락이 걸려있지 않음
LS-->>CS: 락 없음
CS->>LS: 락 설정(menuId)
CS->>R: 메뉴 재고 정보 저장
CS->>LS: 락 해제(menuId)
else 락이 걸려있음
LS-->>CS: 락 존재
Note over CS: 짧은 대기 후 [메뉴ID 확인 및 저장]루프 처음으로 돌아가 재시도
end
end
end
CS->>CS: 장바구니에 메뉴 추가
CS->>CR: 장바구니 저장
CR-->>CS: 저장 완료
CS-->>C: 성공 응답
else 메뉴 없음
CS->>C: MenuNotFoundException 발생
end

sequenceDiagram
participant C as 클라이언트
participant CS as 장바구니서비스
participant R as 레디스
participant DB as 관계형DB
participant LS as 잠금서비스
C->>CS: 장바구니 아이템 처리 요청
loop 각 장바구니 아이템에 대해
loop 메뉴ID 확인 및 처리
CS->>R: {메뉴ID:재고} 존재 여부 확인
alt {메뉴ID:재고}가 레디스에 존재
CS->>R: 원자적으로 재고 감소
alt 재고 < 0
CS->>R: 롤백 (재고 증가)
CS->>C: 재고부족 예외 발생
Note over CS: 재고 부족 이 발생하면 기존 재고 감소를 기록한 리스트를 돌면서 다시 재고를 증가시켜주는 로직을 추가해야함
else 재고 >= 0
CS->>CS: 재고 수정 로그 리스트에 {메뉴ID:수문수량} 기록
end
else {메뉴ID: 재고}가 레디스에 없음
CS->>LS: 락 확인(menuId)
alt 락이 걸려있지 않음
LS-->>CS: 락 없음
CS->>LS: 락 설정(menuId)
CS->>DB: 재고 조회
DB-->>CS: 재고 반환
CS->>CS: 재고 감소
alt 재고 < 0
CS->>CS: (재고 증가)
CS->>R: 재고 정보 저장
CS->>C: 재고부족 예외 발생
Note over CS: 재고 부족 이 발생하면 기존 재고 감소를 기록한 리스트를 돌면서 다시 재고를 증가시켜주는 로직을 추가해야함
else 재고 >= 0
CS->>R: 재고 정보 저장
CS->>CS: 재고 수정 로그 리스트에 {메뉴ID:수문수량} 기록
end
CS->>LS: 락 해제(menuId)
else 락이 걸려있음
LS-->>CS: 락 존재
Note over CS: 짧은 대기 후 [메뉴ID 확인 및 처리]루프 처음으로 돌아가 재시도
end
end
end
Note over CS: [주문 성공 시]<br/> RDS 동기화 옵션 고려:<br/>1. 비동기 업데이트<br/>2. 예약된 업데이트
end
CS->>C: 결과 반환
![]() |
![]() |
---|