🔌 SPARTA/Dev Notes

스케줄러와 Quartz (타임세일 스케줄러 기술 선택)

eunjiom 2026. 4. 21. 15:43

1. 배경

  • 특가 상품은 지정된 시작/종료 시간에 맞춰 상품 상태를 정확히 변경해야 하는 요구사항이 있었음
  • 이를 구현하기 위한 스케줄러 기술을 선택하는 과정에서 아래 네 가지 방식을 검토함

 

2. 검토한 방식

 

1) 조회 시점 계산 방식

  • 상품 조회 시 현재 시간과 saleStartTime, saleEndTime을 비교해 판매전 / 판매중 / 판매종료 상태를 동적으로 판단하는 방식
  • 장점: 구조가 단순하고 스케줄러가 필요 없음
  • 단점
    • 조회 시 BETWEEN saleStartTime AND saleEndTime 조건이 들어가는데, 이 경우 인덱스를 제대로 활용하지 못해 상품 수가 많아질수록 풀스캔이 발생할 수 있음
    • 서버 시간과 DB 시간이 다를 경우, 동일한 상품이 서버에서는 판매중으로 보이고 DB에서는 판매전으로 처리되는 불일치가 생길 수 있음
    • 실제 상품 상태값이 DB에 반영되지 않기 때문에 다른 도메인에서 상태를 재사용하거나 관리자 화면에서 현재 상태를 바로 확인하기 어려움
    • 특가 시작/종료를 상태 변경 이벤트로 다루기보다 응답 시 계산으로 처리하게 되어 도메인 의미가 약해질 수 있음

2)  Redis 기반 예약 키 + 폴링 방식

  • 특가 상품 시작/종료 시간을 Redis에 키 또는 ZSET 형태로 저장한 뒤, 주기적으로 현재 시각과 비교하여 상태를 변경하는 방식
  • Redis가 메모리 기반이라 빠르고, 재고 차감/분산락/동시성 제어 구조와 자연스럽게 연결할 수 있다는 점이 장점이었음
  • 하지만 Redis에 예약 정보를 저장해도 그 시각에 실행할 처리기가 별도로 필요하고, 결국 일정 주기로 예약 키를 확인하는 폴링이 들어가야 함
  • 이는 원하던 정확한 시각에 상태 변경과는 차이가 있었고, 1초나 1분 단위 폴링은 불필요한 반복 조회로 이어질 수 있어 비효율적이라 판단함

3) @Scheduled 기반 폴링 스케줄러

  • Spring의 @Scheduled를 사용해 1분마다 특가 상품 전체를 조회하고, 시작/종료 대상 상품을 찾아 상태를 바꾸는 방식
  • 구현 자체는 단순하지만 전체 상품을 주기적으로 반복 조회해야 하므로 상품 수가 많아질수록 비효율이 커질 수 있음
  • 또한 여러 서버 환경에서는 동일한 스케줄러가 중복 실행될 위험도 존재함
  • 시간이 되면 딱 상태를 바꾸는 방식을 원했기 때문에 최종 선택에서 제외함

4) Quartz 예약 실행 방식

  • 특가 상품을 등록하는 시점에 시작 Job과 종료 Job을 함께 등록해두고, 지정된 시각에 정확히 한 번 실행하여 상태를 변경하는 방식
  • 주기적으로 상품 전체를 확인하지 않아도 되고, 시작 시간에는 READY → ON_SALE, 종료 시간에는 ON_SALE → SALE_ENDED처럼 도메인 상태를 명확히 다룰 수 있음

 

3. 최종 선택 - Quartz

  • 특가 상품의 핵심 요구사항은 정해진 시작 시간에 정확히 판매중으로 전환되고, 종료 시간에 정확히 판매종료로 전환되는 것이었음
  • 따라서 주기적으로 상태를 확인하는 폴링 방식보다, 상품 등록 시점에 시작/종료 작업 자체를 예약하는 방식이 더 적합하다고 판단함

1) 폴링 없이 예약 실행 가능

  • @Scheduled나 Redis 기반 폴링처럼 주기적으로 전체 상품을 조회하지 않고, 상품별 시작/종료 시각에 맞춰 Job을 직접 예약할 수 있음

2) 상태 변경의 의미가 명확함

  • Quartz를 사용하면 시작 시각에 READY → ON_SALE, 종료 시각에 ON_SALE → SALE_ENDED로 실제 상태값이 전환되어 도메인 의미가 분명해짐
  • 조회 시점 계산 방식처럼 서버/DB 시간 불일치 문제나 인덱스 미활용 문제도 발생하지 않음

3) 역할 분리가 자연스러움

  • 특가 시작/종료는 시간 예약 실행 문제이고, 재고 차감/동시성 제어는 동시 요청 처리 문제임
  • 이 둘을 같은 기술로 억지로 해결하기보다 역할을 분리하는 것이 더 자연스럽다고 판단함
  • 시작/종료 예약 → Quartz
  • 재고 차감/동시성/분산락 → Redis

4) 불필요한 반복 조회를 줄일 수 있음

  • Quartz는 상품별 시작/종료 시각에 필요한 작업만 실행하기 때문에, 반복 조회 기반 방식보다 불필요한 부하를 줄일 수 있음

 

4. 쿠폰 스케줄러와 다르게 Quartz를 쓴 이유

  • 쿠폰 스케줄러는 Spring @Scheduled + cron 방식으로 구현했음
  • 타임세일 스케줄러는 Quartz로 구현했음
  • 같은 프로젝트 안에서 스케줄러 기술이 다른 이유는 두 도메인의 성격 자체가 다르기 때문임
  • 구분 쿠폰 스케줄러 타임세일 스케줄러
실행 시점 고정된 주기 (매주 월요일 00시 등) 상품마다 다른 시작/종료 시각
등록 방식 코드에 cron 표현식으로 고정 상품 등록 시점에 동적으로 Job 생성
실행 횟수 반복 실행 각 상품당 정확히 1회 실행
적합한 기술 @Scheduled + cron Quartz
  • 쿠폰은 매주 월요일 00시처럼 반복적이고 고정된 주기에 실행하면 되는 작업임
  • 반면 타임세일은 상품마다 시작 시간과 종료 시간이 다르고, 각각 딱 한 번만 실행되어야 하는 작업임
  • @Scheduled는 미리 정해진 시간에 반복 실행하는 구조라 동적으로 시각을 등록하는 게 어렵고, 상품마다 다른 시각에 맞춰 Job을 만들려면 Quartz처럼 실행 시점을 동적으로 지정할 수 있는 도구가 필요했음
  • 그래서 두 도메인에 각각 성격에 맞는 기술을 선택함