본문으로 건너뛰기

Job Scheduling: BullMQ vs Cloud Scheduler

dha-sleep-api에서 Push 알림 스케줄링에 BullMQ를 선택한 이유와 Cloud Scheduler와의 비교

개요

작업 스케줄링에는 크게 두 가지 접근 방식이 있습니다:

  1. In-Process Queue (BullMQ): 애플리케이션 내부에서 Redis 기반 큐로 처리
  2. External Scheduler (Cloud Scheduler): 외부 서비스가 HTTP 호출로 트리거

dha-sleep-api의 Push 스케줄링에는 BullMQ를 선택했습니다.


아키텍처 비교

BullMQ 방식

┌─────────────────────────────────────────────────────────┐
│ Redis │
│ delayed queue: [job1: 08:00, job2: 09:30, ...] │
└─────────────────────────────────────────────────────────┘

│ polling (매 초)

┌─────────────────────────────────────────────────────────┐
│ dha-sleep-api 서버 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Producer │───▶│ Redis │ │
│ │ (Job 등록) │ │ Queue │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Worker │ │
│ │ (Job 처리) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────┘

특징:

  • 서버가 Redis를 polling하여 시간 도달 시 자동 실행
  • 같은 프로세스 내에서 함수 호출로 처리
  • 외부 네트워크 호출 없음

Cloud Scheduler 방식

┌─────────────────────────────────────────────────────────┐
│ Google Cloud Scheduler │
│ [job1: 08:00 → POST /api/push/send] │
│ [job2: 09:30 → POST /api/push/send] │
└─────────────────────────────────────────────────────────┘

│ HTTP POST (시간 도달 시)

┌─────────────────────────────────────────────────────────┐
│ dha-sleep-api 서버 │
│ │
│ ┌──────────────┐ │
│ │ Controller │ ◀── HTTP 요청 수신 │
│ │ /api/push │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Service │ │
│ │ (Push 발송) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────┘

특징:

  • 외부 서비스가 HTTP로 API 호출
  • 각 Job마다 별도 스케줄러 엔트리 필요
  • 네트워크 통신 필수

상세 비교

1. Job 생성 방식

항목BullMQCloud Scheduler
동적 생성✅ 코드에서 즉시 생성⚠️ API 호출로 생성 (지연 있음)
개별 사용자 스케줄✅ 사용자별 Job 쉽게 생성⚠️ 수천 개 스케줄 관리 어려움
시간 정밀도ms 단위분 단위 (cron)

예시 - 사용자별 Push 예약:

// BullMQ: 간단함
await queue.add('send-push', { userId, title }, {
delay: 3600000, // 1시간 후
jobId: `push-${pushId}`
});

// Cloud Scheduler: 복잡함
await cloudScheduler.createJob({
name: `push-${pushId}`,
schedule: '0 20 * * *', // cron 표현식
httpTarget: {
uri: 'https://api.example.com/push/send',
body: JSON.stringify({ userId, title })
}
});

2. Job 취소

항목BullMQCloud Scheduler
취소 속도✅ 즉시 (Redis 명령)⚠️ API 호출 필요
조건부 취소✅ 데이터 기반 필터링❌ Job 이름으로만 가능

dha-sleep-api 사용 사례:

// 사용자가 대화 시작하면 대기 중인 Push 취소
await pushQueueService.cancelPush(pushId); // 즉시 취소

3. 재시도 및 에러 처리

항목BullMQCloud Scheduler
자동 재시도✅ 설정 가능 (attempts, backoff)⚠️ 제한적
실패 Job 조회✅ 큐에서 직접 조회⚠️ 로그에서 확인
수동 재실행job.retry()❌ 새 스케줄 생성 필요
// BullMQ 재시도 설정
{
attempts: 3,
backoff: {
type: 'exponential',
delay: 5000 // 5초 → 10초 → 20초
}
}

4. 모니터링 및 상태 관리

항목BullMQCloud Scheduler
실시간 상태✅ waiting/active/delayed/completed/failed⚠️ 실행 기록만
Job 데이터 조회✅ 전체 payload 조회 가능❌ 제한적
대시보드Bull Board, ArenaCloud Console

5. 비용

항목BullMQCloud Scheduler
기본 비용Redis 비용만월 $0.10/job + 실행당 비용
대량 Job추가 비용 없음Job 수에 비례
예시 (1만 Job/일)~$50/월 (Redis)~$300/월 이상

6. 인프라 의존성

항목BullMQCloud Scheduler
필수 인프라RedisGCP 프로젝트
서버 다운 시Job 유지 (Redis에 저장)Job 유지 (GCP에 저장)
네트워크 장애✅ 내부 처리로 무관❌ HTTP 호출 실패 가능

dha-sleep-api에 BullMQ가 적합한 이유

1. 사용자별 개인화된 스케줄링

Push Planner Agent가 각 사용자의 대화 컨텍스트를 분석하여 개인화된 시간에 Push를 예약합니다.

// 사용자 A: 오후 8시 30분에 취침 리마인더
// 사용자 B: 오후 10시에 취침 리마인더
// 사용자 C: Push 불필요 (Agent 판단)
  • Cloud Scheduler: 수천 명의 사용자별로 개별 스케줄 생성/관리 필요 → 비현실적
  • BullMQ: 코드 한 줄로 delay 지정하여 즉시 등록 → 간단

2. 조건부 취소 요구사항

"사용자가 먼저 대화를 시작하면 예약된 Push 취소" 기능이 필수입니다.

// push-scheduler.service.ts
async cancelPendingPushes(userId: string): Promise<number> {
// cancelIfUserInitiatesFirst가 true인 Push만 취소
const result = await this.prisma.scheduledPush.updateMany({
where: {
userId,
cancelIfUserInitiatesFirst: true,
status: 'SCHEDULED'
},
data: { status: 'CANCELLED' }
});

// BullMQ Job도 취소
await this.pushQueueService.cancelPush(pushId);
}
  • Cloud Scheduler: 조건부 필터링 불가, 모든 Job 이름을 알아야 취소 가능
  • BullMQ: 데이터 기반 필터링으로 즉시 취소

3. 밀리초 단위 정밀도

Push Planner Agent가 "지금부터 2시간 30분 15초 후"와 같이 정확한 시간을 지정할 수 있습니다.

  • Cloud Scheduler: cron 표현식 (분 단위가 최소)
  • BullMQ: delay: 9015000 (ms 단위)

4. 이미 Redis 사용 중

dha-sleep-api는 세션 관리, 캐싱 등에 이미 Redis를 사용하고 있어 추가 인프라 비용이 없습니다.

5. 실시간 상태 추적

Push 발송 상태를 실시간으로 추적하고 실패 시 즉시 재시도가 가능합니다.

// 큐 상태 조회
const status = await pushQueueService.getQueueStatus();
// { waiting: 5, active: 1, delayed: 23, completed: 1000, failed: 2 }

Cloud Scheduler가 적합한 경우

다음 상황에서는 Cloud Scheduler가 더 적합합니다:

상황이유
고정 시간 배치 작업매일 오전 3시 DB 백업 등
전체 사용자 대상 알림모든 사용자에게 동일 시간에 발송
서버리스 환경Cloud Run 인스턴스가 항상 실행되지 않음
장기 스케줄 (월/년)Redis 메모리 부담 없음

결론

dha-sleep-api Push 스케줄링 요구사항

요구사항BullMQCloud Scheduler
사용자별 개인화 시간⚠️
조건부 취소
ms 단위 정밀도
대량 Job 비용 효율⚠️
실시간 상태 추적⚠️
재시도 전략 커스터마이징⚠️

결론: dha-sleep-api의 Push 스케줄링은 동적이고 개인화된 스케줄링이 핵심이므로 BullMQ가 최적의 선택입니다.


참고: 하이브리드 접근

dha-sleep-api에서는 두 방식을 병행 사용합니다:

용도기술이유
Push 알림 스케줄링BullMQ동적, 개인화, 조건부 취소
일일 리포트 생성Cloud Scheduler고정 시간, 전체 대상
TimeMachine 이벤트Cloud Scheduler시스템 전체 시간 동기화

이렇게 각 기술의 장점을 활용하는 하이브리드 접근이 가장 효과적입니다.