⏰💡 선착순 강의에서 비관적 락(PESSIMISTIC LOCK)을 사용한 이유?

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

선착순 강의 신청 기능을 구현하면서 가장 중요했던 문제는 이것이었다.

정원이 100명일 때, 동시에 여러 명이 마지막 자리를 신청하면 어떻게 될까?

이 문제를 해결하기 위해 나는 비관적 락(PESSIMISTIC_WRITE) 을 사용했다.


1️⃣ findByIdForUpdate는 무엇을 하는가?

Lecture lecture = lectureRepository
    .findByIdForUpdate(lectureId)
    .orElseThrow(() -> new IllegalArgumentException("Lecture not found"));

일반적인 findById()는 단순 조회다.

하지만 findByIdForUpdate()는 다르다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select l from Lecture l where l.id = :id")
Optional<Lecture> findByIdForUpdate(@Param("id") Long id);

이 코드는 실제 SQL로 변환되면:

SELECT * FROM lecture WHERE id = ? FOR UPDATE;

즉,

조회하면서 동시에 해당 row에 쓰기 락을 건다.


2️⃣ 왜 이런 “자물쇠”가 필요한가? (Race Condition 방지)

정원이 100명이고, 현재 99명이 등록되어 있다고 가정해보자.

마지막 1자리를 두 명이 동시에 신청하면?

락이 없다면 이런 일이 벌어진다:

  1. A 유저 → “남은 자리 있나?” → 있음 (99명)
  2. B 유저 → “남은 자리 있나?” → 있음 (99명)
  3. A 유저 → 신청 완료 → 100명
  4. B 유저 → 신청 완료 → 101명 (정원 초과)

이것을 Race Condition(경쟁 상태) 라고 한다.

🔒 비관적 락이 있으면 어떻게 되나?

  1. A 유저 → SELECT ... FOR UPDATE 실행 → row 잠금
  2. B 유저 → 같은 row 접근 시도 → 대기
  3. A 유저 → 정원 차감 후 트랜잭션 종료 → 락 해제
  4. B 유저 → 이제 조회 가능 → 이미 100명 → 신청 실패

즉,

두 번째 사용자는 “조회 단계”에서부터 기다리게 된다.

이게 핵심이다.


3️⃣ 비관적 락(PESSIMISTIC_WRITE)이란 무엇인가?

@Lock(LockModeType.PESSIMISTIC_WRITE)

✅ PESSIMISTIC = 비관적

“동시에 여러 명이 들어올 거라고 가정하고, 처음부터 잠가버리자.”

✅ WRITE = 쓰기 락

“이 row는 내가 끝날 때까지 아무도 수정하지 못하게 하자.”

쉽게 말하면:

내가 이 강의의 정원 처리를 끝낼 때까지,
다른 트랜잭션은 기다려라.


4️⃣ 중요한 오해 하나

“select인데 왜 락이 걸리나요?”

맞다.

일반 select는 락이 걸리지 않는다.

하지만 SELECT ... FOR UPDATE는 다르다.

조회하면서 동시에 해당 row를 쓰기용 잠금 상태로 만든다.


5️⃣ 락은 언제 잡히는가?

락은 이 줄이 실행되는 순간 잡힌다.

lectureRepository.findByIdForUpdate(lectureId);

DB 입장에서 보면:

  • 해당 row에 exclusive lock 획득
  • 트랜잭션 종료까지 유지

그래서 반드시 필요하다:

@Transactional

트랜잭션이 끝나는 시점까지 락이 유지되기 때문이다.

만약 트랜잭션이 없다면?

→ 조회 후 즉시 커밋
→ 락도 즉시 해제
→ 의미 없음


6️⃣ 왜 낙관적 락이 아니라 비관적 락을 선택했는가?

이 상황의 특징은:

  • 선착순
  • 짧은 시간에 동시 요청 집중
  • 정원 초과는 절대 허용 불가

낙관적 락은:

  • 충돌이 적다고 가정
  • 버전 충돌 시 예외 후 재시도

하지만 선착순 상황에서는:

충돌이 “자주” 발생할 가능성이 높다.

따라서:

  • 재시도 로직 복잡
  • 사용자 경험 저하
  • 설계 난이도 증가

그래서 나는:

“충돌이 발생할 가능성이 높다고 가정”하고 비관적 락을 선택했다.


7️⃣ 이 설계의 장점

✅ 정원 초과 100% 방지

✅ 동시성 안전

✅ 구현 직관적

✅ 로직 단순


8️⃣ 이 설계의 단점

  • 락으로 인해 대기 시간 발생 가능
  • 트래픽이 매우 크면 병목 가능
  • DB 부하 증가 가능성

하지만 내 서비스 구조는:

  • Redis로 1차 대기열 처리
  • DB는 “입장권을 가진 소수”만 접근

이기 때문에

실제로 DB에 몰리는 트래픽은 제한적이다.

따라서 비관적 락이 충분히 합리적인 선택이었다.

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

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

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
⏰💡 선착순 강의에서 비관적 락(PESSIMISTIC LOCK)을 사용한 이유?
상단으로

티스토리툴바