문제정의

선착순 쿠폰 1만개를 10만명이 동시에 요청할 때 재고를 어떻게 관리할 것인가?

DB만을 사용해서 트래픽을 감당할 수 있는 방법은 없을까? 

테스트와 동작원리를 파악해나간 의사결정 과정을 기록합니다. 

 

제약 조건 설정

프로젝트의 모티브인 교촌치킨 어플의 선착순 쿠폰 이벤트를 참고하여 제약조건을 설정했습니다. 

1. 초과 발급이 절대로 있어선 안된다.
 - 초과 발급 개수 만큼 비용 손실로 이어지므로 데이터 정합성을 최우선으로 한다.
2. 특정 시간대에 한번에 많은 인원이 몰린다.
 - TPS 3,000 ( 설정 근거: DAU 6만명, 이벤트 참여율 10%로 예상했을 때 2초 이내에 동시 요청을 보낼 것을 대비 )
3. 발급을 요청했는데 응답이 없어 사용자가 답답하게 느껴져선 안된다.
 - 응답시간 800ms 이내 목표 ( 설정 근거: 구글 웹 성능 지표 )

 

1차 실험: DB 직접 방식

100만건의 가데이터를 저장한뒤, 사용자의 쿠폰 발급 이력 조회 쿼리에 EXPLAIN ANALYZE를 활용하여 실행 시간을 측정하였습니다.

사용자 쿠폰 발급 이력 조회 쿼리 처리 시간

 

측정된 처리 시간은 212ms로 약 4.7 TPS의 결과가 나왔습니다.

이는 DB가 1초에 이 쿼리를 5번정도 처리하는 것인데 설정했던 6000명이 동시에 요청을 한다면 마지막 처리까지 21분이 걸리는 심각한 상황임을 확인했습니다. 

 

 

인덱스 최적화를 통해 시간을 단축시키면 되지 않을까? 

인덱스 생성 쿼리를 날리고 다시 쿠폰 발급이력 조회 쿼리 실행시간을 측정했습니다 

// 인덱스를 만들어서 처음부터 끝까지 읽는 방식에서 시간 복잡도 O(log N)으로 단축 
CREATE INDEX idx_user_id ON coupon_history(user_id);

 

처리 시간이 212ms → 1.48ms로 단축되어 단일 쿼리 기준 약 143배의 성능 향상결과를 얻었습니다. 

당연히 목표로 했던 제약조건을 충족시킬 수 있게되었습니다. 

인덱스 적용 후 조회 쿼리 처리시간

 

 

수천개의 동시 요청이 들어올때도 이 성능을 유지할 수 있을까?

동시 접속자 100명이 몰렸을 때, 중복 없이 100개의 요청이 어떻게 처리되는가를 확인할 수 있는 부하테스트 시나리오를 실행했습니다. 

 

 

인덱스 적용된 DB의 재고 조회 부하테스트 결과

  • 최소와 최대의 차이가 크지 않고 모든 요청이 2초대에 몰려 있습니다.
    -> 모든 요청이 빠르게 처리되는 것이 아닌 "줄을 서서 기다리는 시간"이 일정하게 발생하고 있음을 확인했습니다. 
  • TPS는 33/s로 이대로라면 쿠폰 발급을 요청하고 사용자가 멍하니 기다리고 있는 상황이 생길것입니다. 

즉, 인덱스를 적용하여 단일 쿼리 속도를 개선했음에도 불구하고, 100명의 동시 사용자가 몰리자 처리하는데 3분이나 걸리며 목표로한 제약조건을 만족하지 못했습니다.  

 

 

이러한 문제가 발생하는 원인은 무엇일까? 

부하테스트 결과로 DB만으로는 대규모 트래픽을 감당할 수 없음을 알게되었습니다. 

그러나 이러한 문제가 발생하는 근본 원인은 무엇일지 궁금했습니다. 

 

커넥션 풀과 대기시간

데이터베이스와 통신하려면 매번 "연결 → 데이터 처리 → 해제"의 과정을 거쳐야 합니다.
하지만 이 연결 과정 자체가 네트워크를 타고 왔다 갔다 해야 해서 엄청나게 무겁고 시간이 오래 걸립니다.

그래서 미리 연결된 통로들을 바구니에 담아두고 필요할 때마다 꺼내 쓰는 것이 커넥션 풀입니다.

 

갑자기 사용자가 몰리게 되면 미리 정해진 커넥션풀이 부족하며 대기하는 시간이 발생합니다. 

 

하지만 무작적 커넥션 풀을 늘릴수도 없습니다.

서버의 메모리가 꽉차거나 CPU과부하로 서버 다운이 발생할 수 있습니다. 

그래서 어쩔 수 없이 적정 개수로 커넥션 풀을 제한해야하고,
이 제한 때문에 트래픽이 몰리면 대기열이 길어지는 병목 현상이 발생하는 것입니다.

 

 

Redis를 적용하여 부하테스트를 진행했습니다. 

모든 제약조건을 통과한 결과를 얻을 수 있었습니다. 

redis를 사용하여 재고확인 부하테스트 결과

 

  • 결과를 비교했을때 33 TPS -> 88 TPS 로 약 169% 향상되었습니다. 
  • 결과적으로 똑같은 100명의 유저가 돌아가더라도, 한 명당 처리 주기가 짧아지면서 초당 더 많은 요청을 처리할 수 있었습니다. 
지표 DB Redis 개선율
평균 응답 시간 (avg) 2,510ms 767.54ms 약 227% 단축
초당 처리량 (TPS) 33.03/s 88.83/s 약 169% 향상
최대 지연 시간 (max) 2.99s 1.11s 약 170% 단축
p(95) 지표 2.93s 1.11s 약 164% 단축

 

그렇다면 redis는 무엇때문에 이렇게 빠른걸까요? 

 

redis는 인메모리 저장방식으로 설계되어있습니다.

DB의 경우 데이터를 SSD/HDD에 저장하며 정보를 찾으려면 물리적인 하드웨어가 움직여야 하므로 속도에 한계가 있습니다.

하지만, redis는 든 데이터를 RAM에 저장합니다. 전기 신호로만 데이터를 주고받기 때문에 디스크 방식보다 수십에서 수백 배 빠릅니다.

 

뿐만아니라 Redis는 싱글 스레드 방식입니다. 

데이터를 조회하는 과정에서 동시성 이슈를 해결하기 위해 Lock을 걸고 순서를 조절하며 컨텍스트 스위칭 비용이 발생합니다.

하지만 Redis는  한 번에 하나의 일만 처리하는 방식으로 기다리는 과정이 없어 매우 빠릅니다. 

 

결론 

막연히 Redis가 빠르다는 지식에 의존하지 않고, 직접 부하 테스트를 설계하여 DB의 병목 현상을 직접 확인하며 학습할 수 있었습니다. 

어떤 기술을 도입해야할지, 왜 필요한지 의문을 가지고 확인해보는 유의미한 경험이었습니다.

+ Recent posts