본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

대기열 시스템(가상 대기실) 설계

작성 2026. 10. 17.추가
목차10개 항목
출처와 참고

G마켓 기술 블로그의 「지마켓 대기열 시스템 파헤치기」(2022.09)를 바탕으로 재구성하고, Cloudflare Waiting Room 문서와 Redis 문서로 일반 원리를 보탠 노트다.

가상 대기실이란

가상 대기실(virtual waiting room) 은 서비스가 감당할 수 있는 양보다 많은 사용자가 한꺼번에 몰릴 때, 넘치는 사용자를 대기 화면에 세워 두고 정해진 속도로만 들여보내는 장치다. 대형 할인 행사, 티켓 예매, 한정 상품 판매처럼 트래픽이 짧고 뾰족하게 솟는 서비스에 쓴다.

대응문제
서버 증설(scale-out)비용이 크다. 피크가 지나면 늘린 자원은 대부분 논다. DB·외부 결제처럼 늘릴 수 없는 구간이 있으면 효과가 없다
레이트 리밋(rate limit)한도를 넘은 요청을 거절한다. 사용자는 오류를 보고 새로고침을 반복해 부하가 더 커진다
가상 대기실요청을 버리지 않고 순서대로 줄 세운다. 뒷단의 유입량은 일정하게 유지되고, 사용자는 순번을 보며 기다린다

가상 대기실은 처리량을 늘리는 대신 유입량을 조절한다. 뒷단은 평소 용량 그대로 두고 앞단에서 흐름을 깎는다.

구성 요소

구성 요소역할
대기 페이지(프론트엔드)순번·예상 대기 시간을 보여 주고 주기적으로 상태를 다시 묻는다
대기열 API진입 등록, 순번 조회, 입장 가능 여부 판정
이동 작업(job)주기적으로 대기 중인 사용자를 정해진 수만큼 입장 상태로 옮긴다
저장소순번을 빠르게 매기고 조회할 인메모리 저장소(Redis 등)
관리 도구대기열을 켜고 끄고, 초당 유입량을 실시간으로 바꾼다
모니터링대기·입장·종료 인원 추이, 대상 페이지 트래픽

대기열 API는 짧은 요청을 아주 많이 받는다. 그래서 논블로킹 I/O 서버(Node.js 등)로 만들고 컨테이너로 띄워 행사 규모에 맞게 늘린다. 대기열 자체가 가벼워야 뒷단 대신 부하를 받아 낼 수 있다.

왜 Redis Sorted Set인가

Sorted Set은 멤버마다 점수(score) 를 붙여 점수 순으로 정렬된 상태를 유지하는 자료구조다. 대기열에 그대로 대응된다.

대기열 개념Sorted Set
줄키 하나 (waiting)
사용자멤버 (사용자 ID + 대상 상품 등)
도착 순서점수 = 진입 시각(timestamp)
내 순번ZRANK: O(log N)
앞에서 N명 입장ZRANGE 0 N-1로 읽고 ZREMRANGEBYRANK로 지우거나, ZPOPMIN으로 한 번에 꺼낸다
  • RDB에서 매번 ORDER BY로 순번을 세는 것보다 훨씬 빠르다. 추가·순위 조회가 모두 O(log N)이다.
  • 같은 사용자가 다시 들어와도 멤버가 같으면 중복되지 않는다. ZADD NX를 쓰면 처음 진입 시각을 덮어쓰지 않는다.
  • 점수가 같으면 멤버의 사전 순으로 정렬된다. 동시각 도착자의 순서가 문제 되면 점수에 일련번호를 섞는다.

Kafka 같은 메시지 큐는 대기열 저장소로 잘 맞지 않는다. 메시지 큐는 소비자가 꺼내 가는 비동기 처리용이라 「지금 내가 몇 번째인가」를 즉시 답하기 어렵다. 가상 대기실은 순번을 실시간으로 보여 줘야 한다.

동작 흐름

1. 진입: 대기가 필요한가

flowchart TB
  u["사용자"] --> pg["상품 페이지 요청"]
  pg --> q{"대기열 API: 이 상품에 대기열이 켜져 있나?"}
  q -->|"켜져 있지 않음"| agg["대상 트래픽을 집계"]
  agg -->|"임계치 미만"| pass["바로 통과"]
  agg -->|"임계치 이상"| act["대기열 자동 활성화"]
  q -->|"켜져 있음"| zadd["ZADD waiting #lt;now#gt; #lt;userId:itemId#gt;"]
  zadd --> zrank["ZRANK로 순번 확인"] --> wait["대기 페이지로 보낸다"]

대기열을 미리 켜 두는 수동 방식과, 대상(상품·이벤트)별 트래픽을 세다가 임계치를 넘으면 스스로 켜는 자동 방식을 함께 둔다. 예고 없이 터지는 인기 상품은 자동 방식이 잡는다.

2. 대기: 계속 기다려야 하나

대기 페이지는 일정 간격으로 대기열 API에 상태를 묻는다. 대기열이 꺼졌거나 내 멤버가 입장 상태로 옮겨졌으면 원래 페이지로 보낸다.

3. 이동: 주기 작업이 줄을 줄인다

상태를 키 세 개로 나눈다.

키담는 사용자
waiting줄을 선 사용자
running대기열을 통과해 서비스를 쓰는 중인 사용자
finished통과 후 세션이 끝난 사용자
매 주기마다:
  할당량 = 초당 유입량 ÷ 동시에 도는 작업 수
  members = ZRANGE waiting 0 (할당량-1)
  ZADD running <now> members...
  ZREMRANGEBYRANK waiting 0 (할당량-1)
  (별도 작업) 일정 시간이 지난 running → finished

작업을 여러 개 돌릴 때는 전체 유입량을 작업 수로 나눠 나눠 맡긴다. 관리 도구에서 초당 유입량을 바꾸면 다음 주기부터 바로 반영된다. 세 키의 크기 추이가 곧 모니터링 지표가 된다.

관리형 서비스의 기준

CDN이 대기실을 기능으로 제공하기도 한다. Cloudflare Waiting Room이 쓰는 기준이 직접 설계할 때도 참고가 된다.

설정뜻
총 활성 사용자(total active users)대상 페이지에 동시에 머물 수 있는 목표 인원
분당 신규 사용자(new users per minute)새로 들여보내는 최대 속도
세션 시간(session duration)마지막 요청 뒤 몇 분까지 활성 사용자로 셀지
  • 방문자마다 쿠키를 발급해 줄의 위치와 입장 여부를 추적한다. 입장한 사용자는 세션 시간 안이라면 나갔다 와도 다시 줄 서지 않는다.
  • 판정을 가장 가까운 엣지에서 하므로 원 서버(origin)에는 들여보낸 요청만 닿는다.

입장 증표를 쿠키나 서명된 토큰에 담아 두면, 입장 후 요청마다 저장소를 조회하지 않아도 된다.

설계 시 주의점

주의점이유
대기열이 단일 장애점이 된다대기열이 죽으면 서비스 입구가 막힌다. 저장소 복제와 장애 시 우회 정책(전부 통과 또는 전부 대기)을 정해 둔다
우회 차단대상 페이지가 입장 증표 없이도 열리면 대기열이 무의미하다. 뒷단에서도 증표를 확인한다
공정성과 봇먼저 온 순서(FIFO)는 매크로에 유리하다. 봇 차단, 추첨형 입장을 함께 고려한다
대기 경험순번과 예상 시간을 정직하게 보여 준다. 새로고침해도 순번이 유지돼야 이탈과 재진입 폭주가 줄어든다
폴링 부하대기 인원이 많으면 상태 조회만으로도 부하가 된다. 조회 간격을 두고, 순번이 멀수록 간격을 늘린다

정리

  • 가상 대기실은 피크 트래픽을 증설이 아니라 유입량 제어로 다룬다.
  • Redis Sorted Set에 도착 시각을 점수로 넣으면 순번 조회와 앞에서 M명 꺼내기가 각각 O(log N), O(log N + M)으로 끝난다.
  • 대기 → 입장 → 종료 상태를 나누고, 주기 작업이 정해진 유입량만큼 옮긴다. 유입량은 운영 중에 바꿀 수 있어야 한다.
  • 트래픽 임계치로 대기열을 자동으로 켜면 예고 없는 폭주도 막는다.
  • 대기열 자체의 가용성, 우회 차단, 공정성을 함께 설계한다.

관련 노트: 큐와 덱, API 보안 체크리스트, 쿠버네티스 위 웹 서비스의 요청 경로와 구성 요소, 웹 확장성의 구성 요소

원본 기록3

댓글

GitHub 계정으로 댓글을 남길 수 있어요.