MSA-16. 루아스크립트 로직 전환
처음 가상대기열 로직에 대해 어떻게 생각했냐면… 친구들이 좋아하는 가수나 축구, 야구 티켓팅을 매번 도와달라고 해서 (별로 잘하지도 않는데도) 인터파크나 여타 다른 사이트의 티켓팅을 경험해 본 적이 좀 있다. 그래서 내가 티켓팅을 했을 때의 그 프로세스를 그대로 구현하면 어떨까, 라는 생각으로 기획했던 구조가 가상 대기열이라는 구조였다. 놀이공원처럼 큐로 대기열을 제한하고, 재고가 풀리면 다시 그 대기열에 요청을 받아서 선착순 (재고수량) 명만 결제할 수 있게끔…? 취소가 생기면 취소 재고만큼 다시 대기열을 비우고 그 대기열을 다시 채우는 방향으로 생각했다.
다만, 혼자서 너무 오래 생각한 나머지 다른 시선으로 봤을 때 문제가 있진 않을까 - 내가 내 기획에 매몰되어있진 않을까 하는 염려에 멘토님께 해당 로직을 설명드리고 어떻게 생각하시냐 여쭤 봤더니 보통은 분산 락으로 구현한다고는 하나 내가 생각한 로직이 될지 안 될지는 테스트를 직접 해봐야 알 것 같다고 하셨다. 그래서 테스트 툴을 구축해 두 로직을 전부 테스트했다. 이전 테스트 결과에서처럼 분산락은 10개가 성공하였고, 가상대기열 테스트는 실제 재고는 차감되지만 동시성이 보장되지 않아 큐에는 들어갔다. 그러나 결제 프로세스까지 넘어가질 못 했다. 해당 로직은 동시성 보장이 어려웠고, 테스트 결과는 모두 실패했다. 그래서 분산락으로 선택하였으나… 분산락 테스트 과정에서 이상한 점을 발견했다.
위 결과를 보면 무려 42번째로 누른 사람이 가져가버리는 불상사 발생한다. 또한 E열이 요청 시각인데, 보면 밀리세컨드엔 확실히 시간차가 있다.
지금까지 분산 락으로 구현했던 로직의 테스트 결과를 봤는데, 위와 같이 선착순 구매가 되지 않는 것 이었다. 락을 캐치하는 건 결국 랜덤이었던 것… ㅠㅠ 그래서 먼저 접속한(물론 동시라고는 하지만 밀리세컨드에서 차이가 나 요청에는 전부 순서가 있다) 사람이 락을 가져가지 못 해 구매하지 못 하고 있었다. 결국 결과적으론 10명에게 판매가 되었으나 구매 과정에선 엄밀히 말해 선착순은 아니었다! 이는 E-commerce 도메인에서는 꽤나 리스크가 크다고 생각했다. 그리고 가장 처음 테스트했던 건 23초나 걸렸다. 그래서 로직을 다시 변경하였다.
To-be
- 분산 락 제거
- Lua 스크립트의 원자성을 이용하여 분산 락 없이 동시성을 제어
- Lua 스크립트 적용
- 재고 확인, 감소, 주문 생성을 하나의 원자적 연산으로 수행
- Redis에서 모든 작업을 수행해 성능이 향상시킴
- 비동기 처리
- 주문 처리와 재고 동기화를 비동기적으로 수행하여 응답 시간 개선
- 롤백 처리
- 결제 실패 시 Lua 스크립트를 사용해 재고 복구
결국 레디스의 리스트를 다시 사용하고, Lua스크립트를 통해 동시 작업을 진행하여 리스트 사용 + Lua스크립트의 원자성을 통해 동시성을 보장했다. 기획했던 처음의 로직과 비슷하다. 다만 처음 로직과 다른 점은 lua 스크립트를 이용해 재고 확인, 감소, 주문 생성을 동시에 관리하는 부분이다.
추후 주문이 완료되었을 때 Kafka를 통해 SQL 내 재고 차감 이벤트를 발행하는 Write-back 로직은 그대로 가져갔다.
Test Result Summary
- TPS
- As-is: 175.76
- To-be: 235.38
→ 기존 로직 대비 ▲33.92% 향상
- Test Takt time(Average, sec)
- As-is: 23.63681054
- To-be: 11.17878842
→ 10000건의 요청 완료 시간 ▼52.71% 감소
- request duration(Average, sec)
- As-is: 0.2293712116
- To-be: 0.1102334264
→ 10000건 개별 요청 응답 시간 ▼51.94% 감소
테스트 결과도 잘 확인되었다. 추가적으로 로컬에서 실행하였기 때문에 메모리 점유율, CPU 점유율 등도 확인해 봐야 한다.
Comments