서비스

대기열 예매. 대기와 예매 처리를 분리

← HomeLab

티케팅 서비스는 오픈 순간에 사용자가 몰리고 좌석은 4,000석입니다. 예매 처리가 1인당 트랜잭션과 DB 커넥션을 점유해 전체 인원을 그대로 받을 수 없는 구조라, 대기열이 입장 인원을 제한합니다. CGV 팀 프로젝트의 대기열을 queue(Go)와 booking(Spring) 두 서비스, Kafka로 다시 구성했습니다.

서비스 구조

관객 30, 정원 6, 좌석 24
queue Redis waiting, 줄(ZSet) 2 active, 정원 6(ZSet) 1 입장 요청 입장: 빈자리만큼 앞에서 Kafka admissions, 입장 완료 bookings-completed, 자리 반환 메시지 {requestId, movieId} 키 = requestId, 사람별 순서 3 발행: 토픽 뒤에 적재 4 소비: 인증 기록 6 발행: 확정 통지 7 소비: active에서 제거 booking Redis admitted:{사람}, 입장 인증(String, TTL 600초) Kafka로 인증이 온 사람만 좌석 요청 통과 5 좌석 24, 선점에서 확정 MySQL 확정 기록 같은 좌석 두 번 = 거절 선점 확정
정지 상태는 구조도, 재생 시 한 회 흐름을 표시합니다.
정원(active)은 동시 입장 인원이다. 줄(waiting)에서 빈자리만큼 입장하고, 예매 확정 통지로 자리가 반환된다. 관객 30, 정원 6, 좌석 24는 시각화용 축소값이고 실제 정원은 공개 데모(dev) 60, stg 1,000(부하 테스트로 결정).

설계 결정

항목선택이유
서비스 분리대기는 queue(Go), 예매는 booking(Java Spring)대기는 짧고 많은 요청이라 동시 처리 비용이 낮은 Go로, 예매는 결제와 정합성이라 트랜잭션이 검증된 Spring으로
대기열 상태Redis에 줄(waiting)과 정원(active)을 한 곳에어느 queue 파드가 받아도 같은 순번과 정원을 응답
순번 조회폴링. 순번이 뒤일수록 주기를 늘림(1초, 2초, 5초)대기 중 요청의 대부분이 순번 조회라, 먼 순번의 조회 횟수를 줄여 queue 부하를 낮춤
서비스 간 통신Kafka 비동기로 직접 호출 없음동기 호출이면 booking이 느리거나 멈출 때 queue도 함께 멈춤. 처리하지 못한 입장 메시지는 토픽에 남음
좌석 확정MySQL 유니크 제약으로 같은 좌석 두 번 확정을 거절Redis 좌석 선점은 만료나 장애 때 같은 좌석이 중복될 수 있어, 최종 판정은 DB에서
계측코드 계측으로 요청 하나를 trace_id로 서비스 간 추적기본 metric으로는 어느 서비스의 어느 구간이 느린지 알 수 없음

결과

  • queue(Go)와 booking(Spring)을 Kafka로 연결한 대기열 예매 서비스 구현: dev 공개 데모와 stg 부하 테스트를 같은 코드와 차트로 실행. 환경 차이는 값(정원, 커넥션 수, 자원 한도, 주소)뿐
  • 대기와 예매 처리가 분리된 구조: 대기 인원은 queue가 받고 예매 여정은 정원 인원만 booking에서 진행. 예매 여정 요청이 정원에 묶이는 것은 부하 테스트에서 확인

한계

  • Redis 주 노드 1대가 대기열 전담: 5만 명까지 한계에 닿지 않았고(엔진 CPU 37%) 분산 구성은 적용하지 않음
  • 결제는 mock PG: 실제 결제 연동이 없고 좌석 선점부터 예매 완료까지만 구현
  • 서비스 2개 규모의 MSA: 서비스가 많을 때의 서비스 간 통신과 배포 관리(서비스 메시 등)는 경험이 없음

기술 스택

Go, Spring Boot, Flyway, Redis, Kafka(Strimzi), MySQL, OpenTelemetry