⏰ 선착순 강의 대규모 트래픽 처리 – Redis 2차 설계

2026. 2. 15. 00:05·프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트

1차 설계에서는 Redis로 대기열을 구현했다.

  • ZSET으로 선착순 보장
  • Scheduler로 순차 입장 처리
  • admitted SET + TTL로 “입장권” 발급

하지만 여기서 끝이 아니다.

“입장 성공”은 Redis에서의 성공일 뿐,
진짜 신청 확정은 DB에 저장되어야 한다.

그래서 2차 설계의 목표는 이것이었다.


1️⃣ 2차 설계 목표

  1. 대기열 우회 방지 (입장권 검증)
  2. 정원 초과 방지 (동시성 제어)
  3. 중복 신청 방지
  4. 입장권 1회성 사용 보장
  5. 실제 신청 기록 영구 저장

2️⃣ 전체 구조 흐름

Redis 대기열 성공 (admitted)
        ↓
EnrollService.enroll()
        ↓
DB Row Lock (PESSIMISTIC_WRITE)
        ↓
정원 체크 + 차감
        ↓
Enrollment 저장
        ↓
입장권 소비

Redis는 “줄서기” 담당,
DB는 “진짜 확정” 담당.

역할을 명확히 분리했다.


3️⃣ EnrollService – 핵심 로직

@Transactional
public EnrollResult enroll(Long lectureId, String userKey){}

이 메서드가 최종 확정의 핵심이다.

(1) 입장권(admitted) 검증

if(!queueService.isAdmitted(lectureId,userKey)){
    throw new IllegalStateException("입장 권한이 없습니다.");
}

왜 필요한가?

  • 대기열을 우회해 바로 enroll 호출하는 것 방지
  • Redis에서 성공 판정을 받은 사용자만 결제 가능

👉 Redis → DB로 넘어오는 2차 검증

(2) 강의 Row Lock (비관적 락)

@Lock(LockModeType.PESSIMISTIC_WRITE)
Lecture lecture = lectureRepository.findByIdForUpdate(lectureId);

왜 비관적 락을 사용했는가?

정원의 마지막 자리를 여러 명이 동시에 신청하면?

  • A가 seat 확인 → 남음
  • B도 seat 확인 → 남음
  • 둘 다 증가 → 초과 발생

이를 방지하기 위해:

DB Row에 PESSIMISTIC_WRITE 락

  • 한 트랜잭션이 끝날 때까지 다른 트랜잭션 대기
  • 정원 초과 동시성 완전 차단

(3) 중복 신청 방지

if(enrollmentRepository.existsByLectureIdAndUserKey(...)){
    return ALREADY_ENROLLED;
}

그리고 DB 레벨에서도:

@UniqueConstraint(
  name="uk_enrollment_lecture_user",
  columnNames={"lecture_id","user_key"}
)

👉 애플리케이션 + DB 이중 방어

(4) 정원 체크 + 차감

if (!lecture.hasSeat()) {
    throw new IllegalStateException("정원이 마감되었습니다.");
}
lecture.increaseEnrolled();
public boolean hasSeat(){
    return enrolledCount < capacity;
}
  • capacity 초과 방지
  • 락이 걸린 상태에서만 증가

👉 정원 정합성 보장

(5) 신청 기록 저장

enrollmentRepository.save(enrollment);

이게 진짜 성공이다.

Redis 성공이 아니라,
DB에 Enrollment가 저장된 순간이 진짜 확정.

(6) 입장권 소모

queueService.consumeAdmitted(lectureId, userKey);

왜 필요할까?

  • 입장권은 1회성
  • 재사용 불가
  • 중복 결제 방지

👉 “입장 성공 후 결제 시간 제한” 설계 완성


4️⃣ Enrollment 엔티티 설계

@Table(
    uniqueConstraints = @UniqueConstraint(
        name="uk_enrollment_lecture_user",
        columnNames={"lecture_id","user_key"}
    )
)

👉 강의 + userKey 유니크

👉 DB가 중복 신청 차단

@PrePersist
void prePersist(){
    this.createdAt = LocalDateTime.now();
}

👉 신청 시간 기록

👉 receipt 페이지에서 사용


5️⃣ Lecture 엔티티 – 정원 제어

@Column(nullable = false)
private int capacity;

@Column(nullable = false)
private int enrolledCount;

설계 포인트

  • 매번 count(*) 하지 않음
  • enrolledCount 캐싱
  • 락 걸린 상태에서만 증가

👉 성능 + 정합성 균형


6️⃣ 프론트 흐름까지 완성

detail.html

  • 신청 클릭 → /queue
  • userKey localStorage 저장
  • 폴링 /queue/me
  • SUCCESS면 /apply 이동

apply.html

  • 결제 버튼 → /enroll
  • 성공 시 enrolledUserKey 저장
  • receipt 이동

receipt.html

  • /enroll/detail 호출
  • 신청 번호 + 신청 시간 표시

👉 실제 서비스 흐름과 유사한 UX 완성


7️⃣ 1차 설계 vs 2차 설계 차이

구분 1차 2차
줄서기 Redis ZSET 동일
입장 처리 Scheduler 동일
결제 보호 admitted SET 동일
정원 보호 ❌ DB Row Lock
최종 확정 ❌ Enrollment 저장
중복 방지 일부 유니크 제약 + 로직

8️⃣ 내가 이 설계를 통해 고민한 것

✔ 대규모 트래픽에서 DB 보호

  • 줄서기는 Redis
  • 확정만 DB

✔ 동시성 문제 해결

  • Row Lock
  • 정원 초과 방지

✔ 대기열 우회 방지

  • 입장권 검증

✔ 결제 시간 제한

  • admitted TTL

✔ 중복 확정 방지

  • 유니크 제약 + consume

9️⃣ 한 줄 요약

Redis는 “공정한 줄서기”를 담당하고,
DB는 “진짜 확정과 정합성”을 담당한다.

이 구조를 통해:

  • 대규모 트래픽 제어
  • 정원 동시성 문제 해결
  • 서비스 흐름 완성

을 구현했다.

 

'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글

⏰💡 폴링 API 부하테스트 진행 방법 (JMeter)  (0) 2026.02.15
⏰💡 선착순 강의에서 비관적 락(PESSIMISTIC LOCK)을 사용한 이유?  (0) 2026.02.15
⏰🚨 신청 버튼을 누르지도 않았는데 자동으로 신청되는 문제 (Redis 대기열 + localStorage 이슈)  (0) 2026.02.14
⏰ 선착순 강의 대규모 트래픽 처리 – Redis 1차 설계  (0) 2026.02.14
👥🚨 좋아요 상태 변경 후 본인 게시글 삭제 오류 해결 (FK 제약 & DB CASCADE)  (0) 2026.02.14
'프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
  • ⏰💡 폴링 API 부하테스트 진행 방법 (JMeter)
  • ⏰💡 선착순 강의에서 비관적 락(PESSIMISTIC LOCK)을 사용한 이유?
  • ⏰🚨 신청 버튼을 누르지도 않았는데 자동으로 신청되는 문제 (Redis 대기열 + localStorage 이슈)
  • ⏰ 선착순 강의 대규모 트래픽 처리 – Redis 1차 설계
hak0622
hak0622
개발하면서 “이게 뭐지?”라는 순간마다 궁금한 점을 바탕으로 정리한 개발 블로그입니다.
  • hak0622
    궁금한 개발 이야기 Why?
    hak0622
  • 전체
    오늘
    어제
    • 분류 전체보기 (68)
      • 공부 (36)
        • 1. 자바 ORM 표준 JPA 프로그래밍 - 기본.. (35)
        • 시험 (1)
      • 프로젝트 (32)
        • 스프링 부트 3 백엔드 개발자 되기 Blog + .. (32)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
⏰ 선착순 강의 대규모 트래픽 처리 – Redis 2차 설계
상단으로

티스토리툴바