대용량 트래픽을 견디는 콘서트 예약 시스템을 설계해보자

2025. 8. 31. 16:51·💻 Dev/System Design

F-Lab에서 참가하고 있는 블로그 챌린지 2기, 이번 회차에 어떤 내용을 포스팅해볼거냐면~ 

디프만 스터디에서 시스템 디자인 설계 해봤던 내용을 적을 것이다.

 

왜 해보게 됐냐면

최근 토스 NEXT 챌린지나, 스타트업 면접을 보면서 시스템 디자인 설계 능력을 요구하는 곳이 많다는 걸 느꼈다.

이런 설계 능력은 원래 주니어에서 시니어로 넘어갈 때 요구되는 능력이라고 생각했는데,

AI가 등장하면서 시기가 많이 앞당겨진 것 같아 위기감을 느꼈다.. 또륵 😭

 

그래서 디프만에서 열리는 시스템 디자인 설계 스터디에 참여했다!

(회사+ 디프만 프로젝트를 병행하면서 스터디까지 할 수 있을까 싶었지만 일단 갈겨)

 

설계 과제 분석

이번 과제는 콘서트 좌석 예매 시스템을 설계하는 것이고, 상세한 task는 아래와 같다.

더보기

한 공연장의 좌석을 온라인으로 판매하려고 합니다.
사용자는 좌석 배치를 보고 원하는 좌석을 선택한 뒤 결제할 수 있어야 하며, 이미 판매된 좌석은 다시 판매되면 안 됩니다.
또한 좌석을 고른 뒤 일정 시간 안에 결제를 완료하지 않으면 좌석은 자동으로 풀려야 합니다.

가정

  • 단일 이벤트(공연 1회차)만 다룬다.
  • PC 웹 기준(모바일 고려는 선택).
  • 외부 결제는 “결제 성공 콜백이 온다” 가정(실제 결제 연동은 모의).

기능적 요구사항

  • 사용자는 해당 페이지에서, 전체 좌석, 열, 그리고 좌석 상태에 대해 조회 할 수 있다.
  • 좌석은 선택 → 결제 → 확정(판매완료) 의 흐름을 가진다.
  • 결제가 완료된 좌석은 다시 판매될 수 없다.
  • 사용자는 예매가 완료되기까지 동기적으로 응답을 확인해야 한다.
  • 사용자는 자신의 예매 내역을 조회할 수 있다.
  • 사용자는 예매가 완료되기까지 대기열에서 대기해야한다.
  • 좌석 예매와 관련된 로그를 저장하고 조회해야한다.

비기능적 요구사항

  1. 중복 판매가 되어서는 안된다.
  2. 예매가 열리는 순간 피크 사용자가, 20만명이라고 가정한다.
  3. 전체 좌석은 만오천석이라고 가정.
  4. 좌석 조회는 p95,p99 <150ms 이내

이런 거를 해보면 좋아요

  • ERD 설계
  • 아키텍처 다이어그램 그리기

(Optioanl) 추가 고민해 보면 좋을만한 사항들

  • 동시에 여러 사람이 같은 좌석을 선택하면 어떻게 막을까?
  • 결제 실패/타임아웃이 발생하면 좌석 상태를 어떻게 돌려놓을까?
  • 같은 주문 요청이 여러 번 오면(중복 클릭 등) 어떻게 막을 수 있을까?
  • 수십만 명이 동시에 접속하면 어떤 부분이 가장 먼저 병목이 될까?
  • 00명이 대기중입니다.와 같은 대기열을 어떻게 만들 수 있을까?

 

내가 접근한 방법과 설계

사실 과제를 처음 봤을 땐 대체 뭐부터 설계해야 하는 걸까? 하고 되게 막막했다.

 

ERD 설계를 해보라고 했지만, ERD는 기능 요구사항이나 화면 구성에 따라 너무 많이 달라지는 부분이라 생각해서 아키텍처 설계 위주로 접근했다.

 

일단 우선적으로 요구사항은 아래와 같이 정리했다.

동시 접속자 20만명
전체 좌석 수 15,000석
응답시간 P95/P99 < 150ms
중복 판매 절대 금지
예매 플로우 좌석 선택→결제→예약 확정

 

아직은 정량적 수치로 아키텍처를 어떻게 설계해야 하는지 경험이 부족해서, '대용량 트래픽'이라는 관점에서 아래의 `추가 고려사항`들을 중심으로 설계를 진행했다.

 

  • 동시에 여러 사람이 같은 좌석을 선택하면 어떻게 막을까?
  • 결제 실패/타임아웃이 발생하면 좌석 상태를 어떻게 돌려놓을까?
  • 같은 주문 요청이 여러 번 오면(중복 클릭 등) 어떻게 막을 수 있을까?
  • 수십만 명이 동시에 접속하면 어떤 부분이 가장 먼저 병목이 될까?
  • 00명이 대기중입니다.와 같은 대기열을 어떻게 만들수 있을까?

 

1. 동시에 여러 사람이 같은 좌석을 선택하면 어떻게 막을까?

동시에 여러 사람이 같은 좌석을 선택하는 상황을 어떻게 막을지가 가장 핵심적인 문제였다.

굉장히 다양한 방법들이 있지만, 아키텍처에 따라 달라진다.

 

싱글 인스턴스라면 CAS 알고리즘이나 synchronized를 사용할 수 있지만, 실무에서는 대부분 멀티 인스턴스이므로 낙관적 락, 비관적 락, 분산락, 메시지 큐 중에서 선택해야 했다.

 

각 방식의 특징을 정리하면 다음과 같다.

방식 장점 단점 상황
낙관적 락 성능 우수 충돌 시 재시도 비용 높음 충돌이 적은 경우
비관적 락 충돌 상황에서 안정적 락 대기로 인한 성능 저하 일반적인 상황
분산락 즉시 피드백 가능 구현 복잡도 높음 실시간 응답 필요
메시지 큐 공정성 보장 비동기 처리로 즉시성 부족 선착순 이벤트

콘서트 예매의 특성상 다수의 사람들이 소수의 명당(앞줄, 가운데 좌석 등)에 몰리는 현상이 발생한다.

 

이런 상황에서는 동일한 좌석에 대한 충돌이 빈번하게 일어나고, 낙관적 락을 사용하면 대부분의 트랜잭션이 실패해서 재시도해야 한다. 재시도 비용이 누적되면 전체 시스템 성능이 낮아질 수 있다.

 

비관적 락은 충돌이 많은 경우에 낙관적 락보다 성능상 이점은 있다, 근데 아무래도 DB 단까지 와야한 다는 생각에 걸렀는데, "엔티티 단위에만 락을 걸기 때문에 괜찮으려나?"했다.

그래도 낙관적 락과 같은 이유로 명당에 몰리는 이슈때문에 성능상 문제가 있겠다고 생각했다.

 

때문에 최종적으로 분산락, 메시지 큐를 생각했는데, 그 중에서도 콘서트 예매/진짜 선착순(쿠폰 같은) 이벤트 컨셉을 고려해 봤다.

콘서트 예매는 사용자에게 즉시 피드백을 주는 것이 중요하므로, 분산락을 메인 플로우로 하고 메시지 큐를 부가 기능으로 활용하는 것이 적절하다고 결론지었다. 

 

반면, 배달앱 쿠폰처럼 진짜 선착순 이벤트라면 공정성이 우선이므로 메시지 큐를 메인 플로우로 하고 분산락을 중복 요청 방지 보조 역할로 사용하는 게 좋겠다고 생각했다.

 

(하지만 다 이론적인 내용이고 실제로는 부하 테스트를 해보지 않는 이상 모른다고 생각함...)

 

2. "00명이 대기 중입니다"와 같은 대기열 시스템을 어떻게 만들어야 할까?

20만 명의 동시 접속자를 처리하기 위해서는 대기열 시스템이 필수다.

전체 좌석이 15,000석이므로, 실제 예매 가능한 인원은 그보다 훨씬 적다.

 

따라서 시스템이 안정적으로 처리할 수 있는 5,000명 정도만 실제 예매 페이지에 진입시키고, 나머지는 대기열에서 순차적으로 처리하는 방식을 생각했다.

 

대기열 시스템 구현 방법으로는 RabbitMQ나 Kafka 같은 메시지 큐를 처음에 생각했지만, Redis의 Sorted Set을 활용하는 방법도 있다는 걸 알게 되었다.

Redis Sorted Set을 사용하면 타임스탬프를 점수로 하여 자연스럽게 순서를 관리할 수 있고, 대기 순번 조회도 쉽게 구현할 수 있다고 한다.

 

자세히는 아직 잘 몰라서 더 찾아봐야 할 것 같다..

 

3. 결제 실패/타임아웃이 발생하면 좌석 상태를 어떻게 돌려놓을까?

이 부분은 "장애 복구 메커니즘을 구축해야 하지 않을까?" 해서 몇 가지 방법을 고려했다.

 

첫 번째는 Redis TTL을 활용하는 방법이다.

좌석을 선점할 때 Redis에 일정 시간(ex. 10분) 동안만 유지되는 키를 생성하고, 시간이 지나면 자동으로 해제되도록 하는 방식이다.

 

두 번째는 비동기 배치 작업으로 주기적으로 좌석 상태를 체크하는 방법이다. 

결제 대기 상태인 좌석들을 스캔해서 일정 시간이 지난 것들을 자동으로 해제하는 방식이다.

 

세 번째는 PG사에서 결제 실패 콜백이 오면 즉시 복구하는 방법이다. 이벤트 드리븐 방식으로 가장 빠른 복구가 가능하지만, 콜백이 오지 않는 경우를 대비한 로직이 또 필요할 것 같다.

 

4. 같은 주문 요청이 여러 번 오면(중복 클릭 등) 어떻게 막을 수 있을까?

이 부분은 처음에 동시성? 인가 생각했는데 뭔가 그것 보단 중복 요청에 대한 트래픽이라고 생각했다.

 

프론트엔드에서는 1차 방어로 버튼 클릭 시 `disabled` 처리를 해야 한다. 하지만 이것만으로는 충분하지 않을 수 있다.

왜냐면 네트워크 문제나 악의적인 요청 등으로 여전히 중복 요청이 발생할 수 있기 때문이다.

 

백엔드에서는 `Rate Limiter`를 구현해서 1인당 요청 횟수를 제한할 수 있다. 사용자 IP나 세션 기준으로 일정 시간 동안 허용 가능한 요청 수를 제한하는 방식이다

 

가상 면접 사례 4장에 나왔던 처리율 제한 장치를 여기다 써먹는 건가..라고 생각함

 

5. 수십만 명이 동시에 접속하면 어떤 부분이 가장 먼저 병목이 될까?

수십만 명이 동시에 접속하면 어떤 부분이 먼저 병목이 될지 분석해 봤다.

 

첫 번째 병목 지점은 네트워크, 로드밸런서가 아닐까?

가장 앞단에서 20만 명의 동시 접속을 받아야 하므로, 네트워크 대역폭이나 로드밸런서 자체가 먼저 한계에 도달할 가능성이 높다.

이건 대기열 시스템으로 방어할 수 있다.

 

두 번째 병목 지점은 웹 애플리케이션 서버라고 생각한다.

대기열을 통과한 사용자들이 실제 예매 로직을 처리하는 단계에서 CPU와 메모리 사용량이 급증한다.

특히 동시성 제어 로직이나 비즈니스 규칙 검증 과정에서 병목이 발생할 수 있다..

 

세 번째 병목 지점은 데이터베이스다.

디스크 I/O가 메모리 접근보다 훨씬 느리기 때문에 동시 접속자가 많아지면 DB가 병목이 된다.

좌석 상태 조회와 업데이트가 빈번하게 일어나면서 DB 커넥션 풀 고갈이나 락 경합으로 인한 성능 저하가 발생한다.

하지만 스케일 업/아웃은 비용이 많이 들기 때문에, 우선은 현재 리소스 내에서 성능을 개선하는 것이 중요하다.

 

DB 성능 개선 방법으로는 인덱스 최적화로 조회 성능을 개선하고, 읽기 전용 레플리카로 조회 부하를 분산할 수 있다.

하지만 좌석 정보는 실시간 정합성이 중요하므로 캐싱을 적용할 때는 매우 짧은 TTL을 설정하거나 이벤트 기반 캐시 무효화 전략이 필요하다. CQRS 패턴을 적용해서 읽기와 쓰기를 분리하는 것도 고려할 수 있지만, 복잡도가 증가하므로 트래픽 규모와 팀 역량을 고려해서 결정해야 할 듯,,

 

팀원들의 설계 방향

스터디 내는 대학생 분들도 많이 계셨는데, 정말 꼼꼼하고 체계적으로 설계해 오신 모습에 많이 반성했다.. 허허 😖

 

나는 시간이 부족해서 주요 쟁점들만 정리했는데, 다른 분들은 ERD 설계부터 API 명세, 전체 플로우까지 모든 단계를 고려해서 각 병목 지점의 개선 방안을 상세히 준비해 왔다.

요구사항에 나온 정량적 수치를 고려한 부분도 있었고, 내가 놓쳤던 부분들을 많이 다뤄주셔서 자극을 많이 받았다.

아직 레포지토리를 막 만든 상태라 서로 자료가 공유되지 않았는데, 추후에 동의를 구해서 다양한 설계 방안들을 첨부해 수정할 예정이다.

 

 

토론에서 나온 주요 쟁점들

서로 설계 내용을 발표한 후에 공통점도 있었지만 다르게 접근한 부분들이 많았다. 정답이 없는 영역이기 때문에 다양한 설계 방법이 나올 수밖에 없고, 이 부분에서 각자의 기술 선택 근거와 트레이드오프를 공유하면서 서로 성장할 수 있었다.

 

주로 나눴던 3가지 쟁점들을 정리해 봤다.

 

1. 좌석 선점 방식

사용자가 처음 좌석을 클릭했을 때 바로 선점할 것인지에 대한 의견이 갈렸다.

 

KTX나 영화 예매는 좌석을 클릭하면 바로 선점되어서 다른 사용자가 해당 좌석을 선택할 수 없다.

하지만 콘서트 예매를 많이 해본 스터디원이 말하길 인터파크 같은 경우는 좌석을 선점하지 않아서, 여러 사용자가 동일한 좌석으로 결제를 시도할 수 있다고 한다. 이 경우 누가 먼저 결제를 완료하느냐가 관건이 된다.

 

각 방식의 장단점을 정리하면 다음과 같다.

방식 장점 단점
즉시 선점 사용자 경험 좋음, 중복 시도 방지 결제 포기 시 좌석 낭비 가능
결제 시 선점 좌석 활용도 높음 결제 실패 확률 높음, UX 떨어짐

 

지금 다시 생각해 보면 어떤 방식을 선택할지는 비즈니스 정책에 따라 달라질 수 있지 않을까?

사용자 만족도를 우선시한다면 즉시 선점 방식이 좋고, 좌석 판매율을 높이려면 결제 시 선점 방식이 유리하다.

 

2. 트랜잭션 범위

"좌석 선택 → 결제 → 예약 완료" 흐름에서 트랜잭션을 어떻게 나눌지도 중요한 쟁점이었다.

 

트래픽이 적다면 전체를 하나의 트랜잭션으로 처리해도 문제없지만, 동시 접속자가 많으면 네트워크 지연이나 외부 시스템(PG사) 연동 실패 등으로 트랜잭션이 길어질 수 있다. 트랜잭션이 길어지면 락 보유 시간도 길어져서 전체 시스템 성능에 악영향을 준다.

 

실제로 올리브영이나 다른 커머스 기업들의 기술 블로그를 보면 결제 트랜잭션을 분리한 사례들이 많이 나온다. 주문 생성과 결제 처리를 별도 트랜잭션으로 나누고, 이벤트 기반으로 상태를 동기화하는 방식이다.

Spring의 Events를 사용할 수도 있고, 더 나아가면 Kafka, RabbitMQ, Redis Pub/Sub 같은 메시지 브로커를 사용하면 된다고 한다.

 

예시

더보기

- 올리브영 결제 이야기

- 주문 결제 트랜잭션 단위를 어떻게 가져가야 할까

결제 트랜잭션 분리를 구글링 하면 꽤 많이 나오는데, 여기서 이벤트 콜백 기반한 이벤트 프로그래밍 얘기가 많이 나오는 듯하다.

 

나는

사용자가 좌석을 선택해 결제 진행 단계로 들어오는 트랜잭션

-> 결제 주문서 발행 트랜잭션

-> 결제 완료/실패의 콜백을 받고 예약을 완료하는 트랜잭션

이렇게 3단계로 분리하는 게 좋지 않을까?라는 생각을 해봤다.

 

3. 동시성 제어법

가장 오랫동안 토론했던 주제는 동시성을 어떻게 해결할 것인가였다.

멀티 인스턴스를 가정하고 여러 방법의 장단점을 비교했다.

 

DB Unique 제약조건만 사용하는 방법

가장 간단한 방법이다. 테이블에 유니크 제약조건을 걸어서 DB 레벨에서 중복을 방지하는 방식이다. 구현이 간단하고 확실하게 중복을 막을 수 있지만, 동시 요청이 많으면 많은 트랜잭션이 실패해서 사용자 경험이 나빠질 수 있다.

 

비관적 락 사용

트랜잭션 시작 시점에 해당 레코드에 락을 걸어서 다른 트랜잭션이 접근하지 못하게 하는 방식이다. 데드락 위험이 있지만, 충돌이 예상되는 상황에서는 낙관적 락보다 성능이 좋을 수 있다.

 

분산락 활용

Redis나 Zookeeper를 사용해서 애플리케이션 레벨에서 락을 관리하는 방식이다. DB 부하를 줄일 수 있고 세밀한 제어가 가능하지만, 구현 복잡도가 높고 분산락 자체가 장애 포인트가 될 수 있다.

 

각자 다른 방법을 선호했는데, 3시간 동안 토론해도 명확한 결론은 나지 않았다.

결국 시스템의 규모, 예상 트래픽 패턴, 팀의 기술 역량 등을 종합적으로 고려해서 결정해야 할 문제라는 걸 깨달았다.

 

스터디 회고와 개선점

무엇보다  이 주간에 계속 야근+이사+남은 채용 과정들을 치르느라 시간이 너무 없어서 준비를 많이 못해서 아쉽다,,

다음부턴 아키텍처 다이어그램이나 전체적인 타임라인을 도식화해 봐야겠다.

 

또 이번이 스터디 첫 주제라 어떻게 결론을 내려야 할지 명확하지 않아서 서로 트레이드오프를 고려하면서 토론하는 정도로 끝났는데, 앞으로는 좀 더 구체적인 결과물을 만들어보면 좋을 것 같다 🤔

 

예를 들어 각자의 설계를 다이어그램으로 그려서 비교해 보거나, 토론 과정에서 하나의 통합된 아키텍처로 의견을 맞춰본다거나?

참 많이 느끼는 거지만, 대학생/신입분들의 역량이 정말 많이 올라갔다는 걸 많이 느꼈고 좋은 자극으로 받아들이면서 앞으로 정진해야겠다는 동기를 얻었다.

 

 

 

 

 

 

 

저작자표시 비영리 (새창열림)

'💻 Dev > System Design' 카테고리의 다른 글

9장. 웹 크롤러 설계  (0) 2025.07.14
8장. URL 단축기 설계  (0) 2025.07.14
7장. 분산 시스템을 위한 유일 ID 생성기 설계  (0) 2025.07.01
6장. 키-값 저장소 설계  (0) 2025.07.01
5장. 안정 해시 설계  (0) 2025.06.30
4장. 처리율 제한 장치의 설계  (0) 2025.06.30
'💻 Dev/System Design' 카테고리의 다른 글
  • 9장. 웹 크롤러 설계
  • 8장. URL 단축기 설계
  • 7장. 분산 시스템을 위한 유일 ID 생성기 설계
  • 6장. 키-값 저장소 설계
현주먹
현주먹
대구 불주먹 출신 현주먹의 개발.log
  • 현주먹
    현주먹의 개발로그
    현주먹
  • 전체
    오늘
    어제
    • 전체글 (180)
      • 👶🏻 CS (15)
        • Operating System (7)
        • DB (5)
        • Data Structure (2)
        • Software Engineering (1)
      • 💻 Dev (77)
        • Java & OOP (35)
        • Spring (4)
        • DB&JPA (6)
        • System Design (12)
        • Test Code (1)
        • JSP & Servlet (13)
        • Etc (6)
      • 💡 Algorithm (25)
        • 인프런 (9)
        • 백준 (16)
      • 🛠 DevOps & Tool (11)
        • Linux (4)
        • AWS (1)
        • Git (2)
        • Etc (4)
      • 📝 끄적끄적 (52)
        • 후기 및 회고 (11)
        • TDD, 클린 코드 with Java 17기 (3)
        • F-Lab (23)
        • 📖 Clean Code (3)
        • 항해99 코테 스터디 (11)
  • 블로그 메뉴

    • 🐈‍⬛ GitHub
    • TIL repository
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 태그

    항해99
    2025스프링캠프
    오라클
    JPA
    데브클럽
    로또 미션
    ==와 equals()
    개구리책
    NextSTEP
    TDD 클린 코드 with Java
    C
    99클럽
    티스토리챌린지
    에프랩 후기
    코딩테스트준비
    자바의정석
    til
    jsp
    오블완
    코테스터디
    F-Lab
    객체지향
    jsp 2.3 웹 프로그래밍: 기초부터 중급까지
    개발자취업
    개발자멘토링
    백준
    F-Lab 블로그 챌린지
    f-lab 후기
    에프랩
    자바의신절판
  • hELLO· Designed By정상우.v4.10.2
현주먹
대용량 트래픽을 견디는 콘서트 예약 시스템을 설계해보자
상단으로

티스토리툴바