티스토리 뷰

선착순 100명에게 쿠폰을 지급하는 이벤트. 단순해 보이지만, 순간적으로 수만 명이 “광클”을 시작하면 서버는 아수라장이 된다. 이 문제를 해결하기 위해 고민했던 과정과 내가 선택한 해결책을 정리해 본다.


1. 요구사항

  1. 선착순으로 100명에게 할인 쿠폰을 제공한다.
  2. 101개 이상이 지급되면 안 된다.
  3. 순간적으로 몰리는 트래픽을 감당해야 한다.

2. 동시성 제어, 어떤 도구를 쓸 것인가? 

가장 먼저 떠오른 3가지 방법의 한계를 정리해 봤다.

1️⃣ synchronized

synchronized는 JVM 내부에서만 동작하는 락이다. 단일 서버에서는 동시성 문제를 막을 수 있지만, 서버가 여러 대로 확장되면 락이 공유되지 않는다. 그 결과 여러 서버에서 발급 로직이 동시에 실행되어 중복 발급(경쟁 조건)이 발생할 수 있다.

2️⃣ 비관적 락 (대기지옥)

락으로 모든 요청을 순차 처리하면 정합성은 보장된다. 하지만 트래픽이 몰리면 대기 시간이 급격히 증가하고, 지연/타임아웃이 발생해 사용자 경험이 크게 나빠질 수 있다. 소규모라면 가능하지만 대규모 선착순에는 병목이 되기 쉽다.

3️⃣ 낙관적 락 (무한지옥)

version으로 동시 수정 충돌을 감지해 충돌 시 업데이트를 실패시키는 방식이며, 보통 재시도와 함께 사용된다. 하지만 선착순처럼 같은 재고를 동시에 갱신하면 충돌이 잦아져 재시도 지옥으로 빠질 수 있다. 서버 재시도를 하지 않는 전략도 가능하지만, 사용자의 연타/새로고침으로 트래픽이 다시 폭발할 수 있어 별도 제어가 필요하다.


3. 선착순 이벤트에서 정말 중요한 것

쿠폰이 수량을 초과해 발급되지 않는 것이다.

 

선착순에서 핵심은 “누가 1ms 먼저 눌렀는지”가 아니라 수량 정합성이다. 시스템 관점에서는 쿠폰 수량이 초과 발급되지 않도록 보장하는 것이 최우선이며, 모든 요청을 순차 처리할 필요는 없다. 결국 “총 발급 수가 한도를 넘지 않도록”만 보장하면 된다.


4. Redis INCR를 선택한 이유

Redis는 싱글 스레드로 동작하며 명령어가 원자적이다.

 

여러 요청이 동시에 INCR를 호출해도 Redis가 순서대로 처리하므로 값이 꼬이지 않는다. 또한 메모리 기반이라 DB보다 빠르고, 복잡한 분산 락 없이도 IINCR 한 번(원자적 증가)으로 수량 제어가 가능하다.

5. Redis 기반 방식의 주의점

Redis를 쓴다고 끝은 아니다. 예외 케이스를 고려해야 한다.

 

Redis에서는 발급이 성공했지만 DB 저장이 실패하면 수량과 발급 상태가 어긋날 수 있다. 이 경우 메시지 큐를 활용해 비동기로 저장하거나 실패 재처리 전략이 필요하다. 그리고 한 사용자가 연타로 여러 장을 가져가면 안 된다. SADD 같은 Set 기반 중복 체크로 “사용자당 1회”를 보장하는 멱등 설계가 필요하다.


6. 요약

요청이 들어오면 Redis에서 SADD(중복 체크) + INCR(수량 제어)로 먼저 컷 한다. 이후 실제 발급 데이터 저장은 메시지 큐로 분리해 DB 부하를 조절하며 안전하게 처리한다.

 

728x90
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함