수백만 동시접속을 처리하는 대용량 선착순 예매 시스템 아키텍처 설계 및 관련 기술 요소를 빠짐없이 정리한 내용입니다.
[00:00] 파트 1: 대규모 선착순 예매 시스템 기본 아키텍처
1. 문제 배경 및 한계
- 대용량 트래픽 및 DB 한계: KTX/SRT 명절 예매처럼 수십만~수백만이 동시 접속할 때, 일반적인 API - RDB 구조는 DB 과부하로 시스템이 다운됩니다 [01:45].
- RDB의 제약: 최종 예매 시스템은 데이터 무결성과 정합성이 필수적이므로 NoSQL 대신 RDB를 사용해야 하나, RDB는 Scale-Out이 어렵고 Scale-Up 비용이 기하급수적으로 증가합니다 [02:20].
- 핵심 요구사항: 수십만 명의 동시 요청 처리, 선착순 순서 보장, 초과 예매(오버부킹) 방지, 중복 예매 방지 [03:09].
2. 3단계 분리 아키텍처
- 1단계: 대기열 등록 (Redis Sorted Set) [05:08]
- 2단계: 입장 처리 (Scheduler & Active User) [07:45]
- 3단계: 예매 및 동시성 제어 (Redis Lua Script & MQ/RDB) [09:45]
- 원자적 연산: N개 좌석 차감 시 DB Lock으로 인한 부하를 막기 위해 Redis Lua Script를 사용합니다. Lua Script는 멀티스레드 환경에서도 다른 트랜잭션 개입 없이 원자적(Atomic)으로 잔여 좌석 조회 및 차감을 수행하여 중복/초과 예매를 차단합니다 [10:13].
- 부하 완화: 성공한 요청은 API가 RDB에 직접 저장하거나, 부하를 추가 완화하기 위해 MQ(Message Queue)에 적재 후 백그라운드 Worker가 DB에 비동기로 반영합니다 [11:47].
- 결제 분리: 복잡도를 줄이기 위해 예매 시점에는 좌석 예약만 진행하고 결제는 후순위로 분리합니다 [12:15].
[18:31] 파트 2: Q&A - 아키텍처 세부 사항 및 기술 트레이드오프
- 커널/네트워크/웹서버 튜닝 [19:30]: NIC 버퍼, OS 커널 Q 백로그, Nginx 커넥션 수 등 하드웨어 및 OS 계층의 튜닝도 소프트웨어 개선만큼 중요합니다.
- 대기열 이탈자 및 누적 관리 [22:11]: Active User의 누적 부하는 TTL(타임아웃) 설정, 사용자 행동 시점 분산, Redis Lua Script의 최종 잔여 좌석 원자 검증으로 방어합니다.
- 통신 방식 선택 (Socket vs SSE vs Polling) [23:52]: 수십만 커넥션을 지속 유지하는 WebSocket/SSE는 서버 메모리와 파이프라인 자원을 크게 낭비합니다. 단순 순번 조회 요구사항에는 Polling이 가장 적합합니다.
- Lua Script 활용 이유 [25:48]: 단일 자석 차감이 아닌 N장 예매 시 잔여 수량 확인과 차감 연산 사이에 Race Condition이 발생하지 않도록 Atomic 작업으로 묶기 위함입니다.
- 대기 중 이탈자 처리 (Heartbeat vs TTL) [28:22]: Redis ZSET 내 개별 아이템 TTL 불가 제약과 매 Polling 시 쓰기 연산 추가로 인한 부하 때문에, Heartbeat를 매번 갱신하기보다 Active User 이동 후 자연 TTL 소멸시키는 편이 훨씬 효율적입니다.
- Non-Blocking I/O 적용 [32:01]: Spring WebFlux, Netty 등을 사용하여 I/O 대기 병목을 해소하고 쓰레드 자원 효율을 극대화할 수 있습니다.
- 좌석 선점 기능 구현 [37:38]: 특정 좌석 선택 요구사항이 있을 경우, Redis의 SETNX (If Not Exist) 명령어와 TTL을 조합하여 원자적 좌석 선점을 구현합니다.
- Chunked Response [38:24]: 응답 데이터(HTML 등)를 잘게 분할 전송하여 브라우저 연결 끊김을 방지하고 서버 렌더링 메모리 부담을 완화하는 아키텍처 기법입니다.
[43:30] 파트 3: 대기열 구현 시 Redis Sorted Set vs MQ 비교
| 비교 항목 | Redis Sorted Set (ZSET) | Message Queue (MQ) |
|---|---|---|
| 주요 목적 | 실시간 랭킹, 대기열 상태 관리 & UX [49:41] | 비동기 메시지 처리, 버퍼링 & DB 부하 분산 [49:41] |
| 순번 조회 | ZRANK 명령어로 내 대기 위치/전체 인원 즉시 조회 가능 [45:51] | Queue 내부 임의 조회가 불가능함 [45:04] |
| 중복 방지 (멱등성) | 동일 유저 ID(키) 입력 시 1개만 유지, 멱등성 보장 [47:37] | 연타 시 동일 메시지가 계속 중복 적재됨 [47:07] |
| 중간 이탈/삭제 | ZREM 명령어로 대기열 중간의 특정 유저 즉시 제거 가능 [48:37] | Queue 중간 메시지만 지정 삭제가 불가능함 [48:09] |
- 최종 권장 조합: 앞단 대기열 관리 및 순번 조회의 UX에는 Redis Sorted Set을, 대기열 통과 후 실제 처리 및 비동기 저장에는 MQ를 조합하는 것이 표준 아키텍처입니다 [49:04].
[50:07] 파트 4: 실제 SRT 예매 시스템 개발자도구 분석
- 프로토콜 및 크로스 도메인 [50:30]: 레거시 호환성을 위해 HTTP 1.1을 사용하며, 대기열 서버와 예매 서버의 호스트가 분리되어 있습니다 (상용 솔루션 NetFunnel 활용 추정) [51:03].
- 응답 방식 (JSONP) [52:15]: 응답 타입이 application/javascript 형태로 내려옵니다.
- 주요 응답 데이터 [55:27]:
[57:48] 파트 5: 대규모 시스템에서 WebSocket 대신 Polling을 사용하는 이유
1. WebSocket (Stateful)의 치명적 한계
- 메모리 및 커넥션 한계: 50만 명 접속 시 50만 개의 소켓 객체가 메모리에 상주하며, L4/L7 로드밸런서의 Max Connection 한계에 도달합니다 [59:52].
- 재접속 폭풍 (Reconnection Storm): 네트워크가 순간 흔들려 연결이 끊기면, 수십만 명이 동시에 재연결을 시도하여 자체적인 DDoS 공격 상태가 되어 서버가 불능에 빠집니다 [01:00:27].