[FitPass] 스케줄러 시스템 기술적 의사결정

김현정·2025년 6월 27일
0

문제 : 필요한 로직에서 자동화를 시킬 기능이 필요

스케줄러 도입 배경

  • 예약 완료, 이용권 만료, 목표 기간 종료 등 관리자가 직접 처리해야만 하는 수동 상태 관리에 한계점 도달
  • 상태 관리가 안되니 이용권이 만료되어도 사용할 수 있고, 예약이 완료되어도 아직 예약중이라는 상태로 유지, 사용자에게 혼란스러움을 야기시킴
  • 데이터 일관성과 운영 비용을 절감시키기 위해서 자동화를 할 수있는 스케줄러를 도입 결정

의사결정 배경 및 요구사항

비즈니스 요구사항

  • 예약 시스템: 시간 경과에 따른 자동 상태 변경 (CONFIRMED → COMPLETED)
  • 멤버십 관리: 만료 알림 및 자동 활성화로 사용자 경험 향상
  • 목표 관리: 만료된 피트니스 목표 자동 처리
  • 운영 효율성: 수동 관리 업무 최소화

기술적 요구사항

  • 실시간성: 중요한 상태 변경은 즉시 반영
  • 효율성: 시스템 부하 최소화
  • 안정성: 배치 작업 실패 시에도 서비스 정상 동작
  • 확장성: 새로운 자동화 작업 추가 용이

Quartz Scheduler vs : Spring Scheduler

Quartz Scheduler

// Quartz Job 예시
@Component
public class ReservationJob implements Job {
    public void execute(JobExecutionContext context) {
        // 복잡한 설정 필요
    }
}

장점: 클러스터링 지원, 동적 스케줄 변경
단점: 설정 복잡, 오버엔지니어링, 외부 의존성

Spring Scheduler

// 배치 처리 + 실시간 처리 조합
@Scheduled(cron = "0 0 0 * * *") // 백그라운드 일괄 처리
+ 
goal.checkAndUpdateExpiredStatus(); // 조회 시 실시간 체크

장점: 성능 + 정확성 균형
단점: 구현 복잡도 약간 증가

배치 처리로 효율성을 확보하고, 실시간 체크로 정확성을 보장하는 이중 안전장치인 Spring Scheduler로 결정.

아키텍처 설계

┌─────────────────────────────────────────────────────────────┐
│ 백그라운드 배치 처리 (Spring Scheduler)                          │
│ • 매일 자정: 만료된 목표 일괄 처리                                 │
│ • 매시간: 예약 상태 자동 변경                                     │
│ • 매일 오전: 만료 알림 발송                                       │
└─────────────────────────────────────────────────────────────┘
                            +
┌─────────────────────────────────────────────────────────────┐
│ 실시간 처리 (사용자 액션 시)                                      │
│ • 목표 조회 시: 만료 상태 즉시 체크                                │
│ • 예약 조회 시: 현재 상태 실시간 반영                               │
└─────────────────────────────────────────────────────────────┘

구현 설계 결정사항

1. 스케줄러 도메인별 분리

// 도메인별 책임 분리로 유지보수성 향상
ReservationScheduler    → 예약 관련 자동화
MembershipScheduler     → 멤버십 관련 자동화  
FitnessGoalScheduler   → 목표 관련 자동화

의사결정 이유 : 단일 책임 원칙, 코드 가독성, 확장 용이성

2. 실행 시간 최적화 전략

// 시스템 부하 분산을 위한 시간대별 분리
00:00 (자정)    → 목표 만료 처리 (하루 단위 정리)
01:00 (새벽)    → 장기 대기 예약 취소 (정리 작업)
06:00 (새벽)    → 멤버십 자동 활성화 (사용 전 준비)
09:00 (오전)    → 만료 알림 발송 (사용자 활동 시간)
매시간 00분     → 예약 상태 변경 + 2시간 전 알림

의사결정 이유 :

  • 서버 리소스 집중 방지
  • 사용자 알림 최적 시간대 고려
  • 비즈니스 로직에 맞는 처리 순서

3. 하이브리드 상태 관리 패턴

// 실시간 + 배치 처리 조합
@Entity
public class FitnessGoal {
    // 실시간 체크 (조회 시마다)
    public void checkAndUpdateExpiredStatus() {
        if (goalStatus == GoalStatus.ACTIVE && LocalDate.now().isAfter(endDate)) {
            this.goalStatus = GoalStatus.EXPIRED;
        }
    }
}

@Scheduled(cron = "0 0 0 * * *") // 배치 처리 (백그라운드)
public void updateExpiredGoals() {
    List<FitnessGoal> activeGoals = repository.findByGoalStatus(ACTIVE);
    activeGoals.forEach(FitnessGoal::checkAndUpdateExpiredStatus);
}

의사결정 이유:

  • 즉시성: 사용자 액션 시 실시간 반영
  • 효율성: 백그라운드 일괄 처리로 DB 부하 최소화
  • 안정성: 이중 체크로 누락 방지

4. 사용자 경험 최적화 알림 전략

// 단계적 알림으로 사용자 이탈 방지
멤버십 만료: 3일 전 → 1일 전 → 당일
예약 알림: 하루 전 → 2시간 전
목표 처리: 자정 일괄 (사용자 수면 시간)

의사결정 이유 : 사용자 준비 시간 확보, 이탈률 감소

5. 트랜잭션 및 예외 처리 전략

// 읽기 전용 최적화
@Transactional(readOnly = true)
public void sendThreeDaysBeforeExpirationNotice() { ... }

// 쓰기 작업 일관성 보장
@Transactional
public void completeExpiredReservations() { ... }

// 안전한 예외 처리
try {
    fitnessGoalService.updateExpiredGoals();
    log.info("처리 완료");
} catch (Exception e) {
    log.error("처리 중 오류 발생", e); // 서비스 중단 없이 로깅
}

실제 구현 성과

예시)

1. 자동화 된 작업들

  • 예약 관리: 시간 경과 시 자동 완료 처리
  • 멤버십 관리: 만료 3단계 알림 + 자동 활성화
  • 목표 관리: 만료된 목표 자동 정리
  • 장기 대기 예약: 24시간 후 자동 취소

2. 시스템 효율성

// Before: 수동 관리
- 관리자가 직접 상태 변경
- 사용자 문의 증가
- 데이터 불일치 가능성

// After: 자동화
- 무인 상태 관리
- 사용자 만족도 향상
- 데이터 일관성 보장

안정성 보장 설계

1. 실패 격리

// 각 스케줄러는 독립적으로 동작
ReservationScheduler 실패 → MembershipScheduler 정상 동작

예약 스케줄러가 무너져도 다른 스케줄러는 살아있기에 독립적으로 동작한다.

2. 멱등성 보장

// 같은 작업을 여러 번 실행해도 결과 동일
if (goalStatus == GoalStatus.ACTIVE && LocalDate.now().isAfter(endDate)) {
    this.goalStatus = GoalStatus.EXPIRED; // 이미 EXPIRED면 변경 없음
}

3. 로깅 및 모니터링

log.info("만료된 예약 {}개를 COMPLETED로 변경합니다.", expiredReservations.size());
log.info("사용자 ID: {}, 멤버십: {} 자동 활성화 완료", userId, membershipName);

핵심 의사결정 요인

  • 단순함 > 복잡함: 현재 요구사항에 과도한 기술 사용 지양
  • 성능 + 정확성: 하이브리드 방식으로 두 마리 토끼
  • 사용자 경험: 기술적 자동화를 통한 서비스 품질 향상
  • 운영 효율성: 수동 작업 최소화로 인적 비용 절감
  • 확장 가능성: 새로운 자동화 요구사항 대응 준비

0개의 댓글