점진적으로 선착순 티켓팅 발급 서비스 성능을 개선하는 설계 방식에 대해 알아본다.
(주로 그림을 통해 설명하고자한다.)
비지니스 요구사항
간단하게 고객(user)와 티켓(ticket)의 도메인이 존재한다고 가정한다.
고객은 동일한 중복 티켓은 발급받을 수 없고, 티켓은 최대 1000개까지만 발급할 수 있도록하는 제한사항이 있다.
티켓 발급 이벤트가 시작되면, 매우 많은 트래픽을 받게된다.
고려하지 않은 내용
이번 글에선 레디스로 캐싱과 분산 처리를 통해 성능 개선을 하는 방식을 고민하면서 설계해보았기에 데이터베이스 잠금은 고려하지않는다.
첫번째 설계
가장 일반적인 전통적인 방식을 고려해보았다.
클라이언트가 티켓 발급 명령을 보내면, api server는 검증로직을 거친 뒤 데이터베이스에 필요한 데이터들을 영속하는 구조이다.
가장 심플하지만, 트래픽이 증가할수록 application, database의 cpu 상승과 더불어, 아주 치명적인 데이터 정합성 문제가 발생한다.
바로 "1번 발급 가능한 티켓 조회" 단계에서 (디비 락을 사용하는게 아니라면) 동시성 문제를 경험하게 된다.
동시성 발생 상황은 아래와 같다.
* 스레드 A, B가 있다고 가정한다.
현재 티켓 발급 테이블엔 총 1000개 중 2개만 발급된 상태이다.
A, B 요청이 동시에 테이블을 조회한다.
A, B 스레드는 2개만 발급되었다는 컨텍스트를 가지고 다음 로직을 실행하게된다.
이를 통해, 우리의 요구사항인 최대 1000개까지만 발급할 수 있다는 논리가 깨지면서 치명적인 데이터 정합성 이슈가 발생한다.
위 문제를 해결하기 위해 다음 설계를 고민해본다.
두번째 설계
레디스의 분산락을 이용하여, 동시성 문제를 해결한다.
이제 앞단에서 redis instance를 이용하여 application이 스케일 아웃 되더라도 동시성 문제는 잡을 수 있게되었다.
그런데 아직 요구사항 중 우려되는 지점이 하나 존재하는데 매우 많은 트래픽 상황을 고려해야한다.
위 구조는, 모든 트래픽을 api server -> database 형태로 받게되고, lock을 잡는 로직에서 많은 시간을 소비하게된다.
RPS가 더 나아지도록 다음 구조 개선을 고민해본다.
세번째 설계
위 구조의 흐름은 다음과 같다.
클라이언트가 티켓 발급 명령을 보낸다.
api server는 in memory db인 redis에 조회, 저장 제어를 통해 저장한다.
이렇게 클라이언트의 요청은 성공으로 끝나게된다.
이제 consumer server는 주기적으로 cron job을 돌면서 redis에 정보를 조회하고 발급 가능한 티켓이 존재한다면 발급한다.
물론 발급한 내용은 데이터베이스에 저장된다.
여기서 어색한 점은 "이렇게 클라이언트의 요청은 성공으로 끝나게된다." 일 수 있다.
현재 요구사항에선 요청 즉시 티켓팅 성공 여부를 판단할 수 있다/ 없다의 내용이 존재하지않는다.
만약 바로 여부를 판단할수 있다면 세번째와 같은 구조는 나올 수 없다.
이를 일반적으로 최종적 일관성(eventual consistency)하다고 말할 수 있고, 반대로 두번째 구조와 같이 요청에 바로 응답을 줄 수 있는 구조를 강한 일관성(strong consistency)라고 한다.
(이 내용은 이미 많은 주제로 다뤄지고있다. ref. https://www.baeldung.com/cs/eventual-consistency-vs-strong-eventual-consistency-vs-strong-consistency)
위 일관성 내용을 간단히 말해보자면,
강한 일관성은 요청 -> 응답이 동기로 이루어져 바로 결과를 알 수 있도록 구성하는 방식이고,
최종적 일관성은 비동기로 최종적으로(언젠간) 처리된다라는 메커니즘으로 구성하는 방식을 말한다.
위 두개의 차이점은 확실하다. 이 내용을 토대로 제품 오너와 이야기를 통해 정책을 새로 마련하는것도 좋은 방법이다.
여기선 많은 트래픽을 처리하는 구조와 티켓팅 특성상 강한 일관성을 반드시 보장시켜야하는건 아니라고 판단되어서 제품 오너와 이야기를 통해 이야기를 해보는것을 토대로 설계해보았다.
티켓팅 발급 서비스를 구현하면서 겪을 수 있는 동시성 문제와 높은 가용성을 확보할 수 있는 서비스를 만들때 고려해야할 사항들을 작성해보았다.
(잘못된 내용에 대한 지적과 비판적인 코멘트 대환영입니다!)
'Architecture' 카테고리의 다른 글
Bucket4j를 이용하여 Rate Limiting Pattern 구현하기 (3) | 2024.09.08 |
---|---|
Redis로 구현한 Rate Limiting Pattern (5) | 2024.09.02 |
메세지 브로커로 동시성 이슈 트러블 슈팅하기 (feat. AWS SQS) (0) | 2024.05.10 |
이벤트 소싱을 사용한 이유, 그리고 적용하면서 겪은 문제들 (feat. CQRS) (1) | 2024.05.02 |
댓글