NestJS Scheduling 구현 방법 5가지 비교 분석
결론
NestJS에서 스케줄링 작업을 구현할 수 있는 5가지 주요 방법은 다음과 같습니다.
- @nestjs/schedule - NestJS 공식 모듈로 cron 패키지 기반의 데코레이터 방식 스케줄링을 제공합니다 (NestJS 공식 문서 (opens in a new tab))
NestJS 공식 문서에서 "The @nestjs/schedule package provides decorators for declarative cron jobs, timeouts and intervals" 라고 명시되어 있습니다.
- BullMQ - Redis 기반 큐 시스템으로 분산 환경에서 작업 영속성과 강력한 작업 관리 기능을 제공합니다 (BullMQ NestJS 가이드 (opens in a new tab))
BullMQ 공식 문서에서 "BullMQ is a library for managing job queues with powerful features like rate-limiting, job retries, scheduling, and more" 라고 명시되어 있습니다.
- Agenda - MongoDB 기반으로 유연한 스케줄링 문법과 작업 영속성을 제공합니다 (Better Stack 비교 가이드 (opens in a new tab))
Better Stack 가이드에서 "Agenda offers functionality for flexibly scheduling jobs using both cron and human-readable syntax" 라고 명시되어 있습니다.
- node-schedule - 경량 라이브러리로 시간 기반 스케줄링에 특화되어 있습니다 (LogRocket 비교 (opens in a new tab))
LogRocket에서 "Node-schedule is mainly for time-based rather than interval-based scheduling" 이라고 명시되어 있습니다.
- Bree - Worker threads를 활용하여 Node와 브라우저 양쪽에서 작동하는 최신 스케줄러입니다 (Better Stack 가이드 (opens in a new tab))
Better Stack에서 "Bree runs both in Node and in the browser, using worker threads in Node and web workers in the browser" 라고 명시되어 있습니다.
1. @nestjs/schedule
구현 방법
npm install --save @nestjs/schedule
npm install --save-dev @types/cronimport { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';
@Module({
imports: [ScheduleModule.forRoot()],
providers: [TasksService],
})
export class AppModule {}import { Injectable } from '@nestjs/common';
import { Cron, Interval, Timeout } from '@nestjs/schedule';
@Injectable()
export class TasksService {
// 매일 자정에 실행
@Cron('0 0 * * *', {
timeZone: 'Asia/Seoul',
})
handleDailyCron() {
console.log('일일 작업 실행');
}
// 5초마다 실행
@Interval(5000)
handleInterval() {
console.log('5초 간격 작업');
}
// 앱 시작 후 10초 뒤 실행
@Timeout(10000)
handleTimeout() {
console.log('지연 작업 실행');
}
}동적 스케줄링 (NestJS 공식 문서 (opens in a new tab)):
import { Injectable } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class DynamicTasksService {
constructor(private schedulerRegistry: SchedulerRegistry) {}
addCronJob(name: string, cronTime: string) {
const job = new CronJob(cronTime, () => {
console.log(`${name} 실행`);
});
this.schedulerRegistry.addCronJob(name, job);
job.start();
}
deleteCronJob(name: string) {
this.schedulerRegistry.deleteCronJob(name);
}
}장점
- NestJS와 완벽한 통합 (공식 모듈)
- 간단한 데코레이터 기반 문법
- 추가 인프라 불필요 (Redis, MongoDB 등)
- 타임존 지원
- 동적 스케줄링 가능 (SchedulerRegistry)
단점
- 작업 영속성 없음 (서버 재시작 시 스케줄 초기화)
- 수평 확장 시 각 인스턴스마다 cron job이 실행되는 문제 (DEV Community (opens in a new tab))
DEV Community에서 "A key issue with @nestjs/schedule is that it creates one Cronjob for every instance of your application" 이라고 명시되어 있습니다.
- 프로덕션에서 간헐적으로 스케줄 함수가 멈추는 이슈 보고됨 (GitHub Issue (opens in a new tab))
GitHub 이슈에서 "Some scheduled functions stop running without error messages in production" 라고 보고되어 있습니다.
적합한 사용 사례
- 단일 서버 환경
- 간단한 정기 작업 (로그 정리, 캐시 갱신)
- 프로토타이핑 단계
2. BullMQ
구현 방법
npm i bullmq @nestjs/bullmqRedis 설치 필요:
# Docker 사용 시
docker run -d -p 6379:6379 redisimport { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
@Module({
imports: [
BullModule.forRoot({
connection: {
host: 'localhost',
port: 6379,
},
}),
BullModule.registerQueue({
name: 'tasks',
}),
],
})
export class AppModule {}프로세서 정의 (BullMQ NestJS 가이드 (opens in a new tab)):
import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq';
import { Job } from 'bullmq';
@Processor('tasks')
export class TasksProcessor extends WorkerHost {
async process(job: Job): Promise<any> {
switch (job.name) {
case 'send-email':
return this.sendEmail(job.data);
case 'generate-report':
return this.generateReport(job.data);
}
}
private async sendEmail(data: any) {
console.log('이메일 전송:', data);
}
private async generateReport(data: any) {
console.log('보고서 생성:', data);
}
@OnWorkerEvent('completed')
onCompleted(job: Job) {
console.log(`작업 완료: ${job.id}`);
}
@OnWorkerEvent('failed')
onFailed(job: Job, error: Error) {
console.log(`작업 실패: ${job.id}`, error);
}
}반복 작업 스케줄링:
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
@Injectable()
export class TasksService {
constructor(@InjectQueue('tasks') private tasksQueue: Queue) {}
async scheduleRepeatingTask() {
await this.tasksQueue.add(
'send-email',
{ recipient: 'user@example.com' },
{
repeat: {
pattern: '0 0 * * *', // 매일 자정
},
},
);
}
}@nestjs/schedule과 조합 (DEV Community (opens in a new tab)):
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
@Injectable()
export class SchedulerService {
constructor(@InjectQueue('tasks') private tasksQueue: Queue) {}
@Cron('0 0 * * *')
async scheduleDailyTask() {
// 스케줄러는 하나의 인스턴스에서만 작업을 큐에 추가
// 실제 처리는 여러 워커에서 분산 처리
await this.tasksQueue.add('daily-report', {});
}
}장점
- Redis 기반 작업 영속성
- 분산 시스템에 최적화 (여러 인스턴스에서 작업 중복 실행 방지)
- 강력한 기능:
- Rate limiting
- 작업 재시도 (BullMQ 문서 (opens in a new tab))
- 우선순위 큐
- 지연 작업
- Dead Letter Queue (DLQ) (DEV Community (opens in a new tab))
- Bull Board를 통한 모니터링 UI 제공
- TypeScript로 재작성되어 타입 안정성 우수 (Medium (opens in a new tab))
Medium 아티클에서 "Bull and BullMQ are queue libraries that persist jobs in Redis, but Bull is in maintenance mode with only bug fixes, while BullMQ is rewritten in TypeScript" 이라고 명시되어 있습니다.
단점
- Redis 인프라 필요 (운영 복잡도 증가)
- 단순 스케줄링에는 과도한 설정
- 초기 설정이 @nestjs/schedule보다 복잡
- Redis 비용 발생 가능
적합한 사용 사례
- 분산 환경 (여러 인스턴스)
- 작업 영속성이 중요한 경우
- 복잡한 작업 관리 필요 (재시도, 우선순위, 지연)
- 이미 Redis를 사용 중인 프로젝트
- 작업 모니터링이 필요한 경우
3. Agenda
구현 방법
npm install agendaMongoDB 연결 및 설정:
import { Injectable, OnModuleInit } from '@nestjs/common';
import Agenda from 'agenda';
@Injectable()
export class AgendaService implements OnModuleInit {
private agenda: Agenda;
constructor() {
this.agenda = new Agenda({
db: { address: 'mongodb://localhost:27017/agenda' },
processEvery: '1 minute',
});
}
async onModuleInit() {
// 작업 정의
this.agenda.define('send daily report', async (job) => {
console.log('일일 보고서 전송');
});
this.agenda.define('cleanup logs', async (job) => {
const { days } = job.attrs.data;
console.log(`${days}일 이전 로그 정리`);
});
await this.agenda.start();
// 스케줄링
await this.agenda.every('0 0 * * *', 'send daily report');
await this.agenda.every('5 minutes', 'cleanup logs', { days: 7 });
}
async scheduleOneTimeJob(when: Date, name: string, data: any) {
await this.agenda.schedule(when, name, data);
}
async cancelJob(name: string) {
await this.agenda.cancel({ name });
}
}장점
- MongoDB 기반 작업 영속성
- 유연한 스케줄링 문법 (cron + 자연어) (Better Stack (opens in a new tab))
- 작업 우선순위, 동시성 제어 지원
- 성숙한 라이브러리
- 분산 환경에서 작업 중복 실행 방지
단점
- MongoDB 인프라 필수
- NestJS 공식 통합 패키지 없음 (직접 래핑 필요)
- Bull/BullMQ보다 기능이 제한적
- 유지보수가 Bull/BullMQ만큼 활발하지 않음
적합한 사용 사례
- MongoDB를 이미 사용 중인 프로젝트
- 유연한 스케줄링 문법이 필요한 경우
- 분산 환경에서 작업 영속성 필요
4. node-schedule
구현 방법
npm install node-scheduleimport { Injectable, OnModuleInit } from '@nestjs/common';
import * as schedule from 'node-schedule';
@Injectable()
export class ScheduleService implements OnModuleInit {
onModuleInit() {
// Cron 문법
schedule.scheduleJob('0 0 * * *', () => {
console.log('매일 자정 실행');
});
// Date 객체로 정확한 시간 지정
const date = new Date(2025, 11, 15, 14, 30, 0);
schedule.scheduleJob(date, () => {
console.log('2025년 12월 15일 14시 30분에 실행');
});
// RecurrenceRule로 반복 규칙 정의
const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = [0, 6]; // 일요일, 토요일
rule.hour = 9;
rule.minute = 0;
schedule.scheduleJob(rule, () => {
console.log('주말 오전 9시 실행');
});
}
scheduleDynamicJob(name: string, cronTime: string, callback: () => void) {
const job = schedule.scheduleJob(name, cronTime, callback);
return job;
}
cancelJob(job: schedule.Job) {
job.cancel();
}
}장점
- 경량 라이브러리
- 추가 인프라 불필요
- 시간 기반 스케줄링에 최적화 (LogRocket (opens in a new tab))
- Date 객체로 정확한 시간 지정 가능
- cron보다 더 유연한 반복 규칙 설정 가능 (RecurrenceRule)
단점
- 작업 영속성 없음
- 분산 환경 미지원
- @nestjs/schedule보다 NestJS 통합이 덜 자연스러움
- interval 기반 스케줄링보다는 시간 기반에 초점
적합한 사용 사례
- 정확한 시간 기반 스케줄링 필요
- 간단한 단일 서버 환경
- 추가 인프라 없이 경량 솔루션 필요
5. Bree
구현 방법
npm install bree작업 디렉토리 구조:
jobs/
daily-report.js
cleanup-logs.js작업 파일 예시 (jobs/daily-report.js):
const { parentPort } = require('worker_threads');
async function generateReport() {
console.log('보고서 생성 중...');
// 무거운 작업 수행
await new Promise(resolve => setTimeout(resolve, 5000));
console.log('보고서 생성 완료');
if (parentPort) parentPort.postMessage('done');
}
generateReport();Bree 서비스 설정:
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import Bree from 'bree';
import path from 'path';
@Injectable()
export class BreeService implements OnModuleInit, OnModuleDestroy {
private bree: Bree;
constructor() {
this.bree = new Bree({
root: path.join(__dirname, 'jobs'),
jobs: [
{
name: 'daily-report',
cron: '0 0 * * *',
},
{
name: 'cleanup-logs',
interval: '1h',
},
],
});
}
async onModuleInit() {
await this.bree.start();
}
async onModuleDestroy() {
await this.bree.stop();
}
async addJob(name: string, options: any) {
await this.bree.add({ name, ...options });
await this.bree.start(name);
}
async removeJob(name: string) {
await this.bree.remove(name);
}
}장점
- Worker threads 사용으로 메인 스레드 블로킹 방지 (Better Stack (opens in a new tab))
- Node와 브라우저 모두 지원
- 다른 스케줄러보다 많은 기능 제공
- 상대적으로 최신 라이브러리
- 작업 격리로 안정성 향상
Better Stack에서 "Bree runs both in Node and in the browser, and despite being younger, offers more features than most other schedulers" 라고 명시되어 있습니다.
단점
- 작업을 별도 파일로 관리해야 함 (구조 복잡도 증가)
- NestJS 공식 통합 없음
- 작업 영속성 없음 (직접 구현 필요)
- 상대적으로 덜 알려진 라이브러리 (커뮤니티 작음)
적합한 사용 사례
- CPU 집약적 작업을 별도 스레드에서 실행
- 작업 격리가 중요한 경우
- 최신 기술 스택 선호
비교 요약
확장성 (분산 환경)
- BullMQ (최고) - Redis 기반, 분산 환경 설계
- Agenda (높음) - MongoDB 기반, 분산 지원
- Bree (중간) - Worker threads 활용하지만 영속성 없음
- @nestjs/schedule (낮음) - 각 인스턴스마다 실행
- node-schedule (낮음) - 단일 인스턴스
안정성
- BullMQ (최고) - 작업 영속성, 재시도, DLQ
- Agenda (높음) - 작업 영속성
- Bree (중간) - Worker threads 격리
- node-schedule (중간) - 성숙한 라이브러리
- @nestjs/schedule (낮음) - 프로덕션 이슈 보고됨
학습 곡선 (낮음 → 높음)
- @nestjs/schedule - NestJS 데코레이터 패턴, 가장 직관적
- node-schedule - 간단한 API
- Bree - 별도 파일 구조 이해 필요
- Agenda - MongoDB 연동 및 API 학습
- BullMQ - 큐 개념, Redis, 프로세서/워커 분리 이해
인프라 요구사항
| 방법 | 필수 인프라 | 비용 |
|---|---|---|
| @nestjs/schedule | 없음 | 낮음 |
| node-schedule | 없음 | 낮음 |
| Bree | 없음 | 낮음 |
| BullMQ | Redis | 중간 |
| Agenda | MongoDB | 중간 |
선택 가이드
프로젝트 초기 / 단순한 요구사항
→ @nestjs/schedule (단, 프로덕션 신뢰성 이슈 인지)
→ 대안: node-schedule (더 안정적)
분산 환경 / 프로덕션 중요
→ BullMQ (이미 Redis 사용 중이거나 도입 가능)
→ Agenda (MongoDB 사용 중)
CPU 집약적 작업
→ Bree (worker threads 활용)
→ BullMQ (별도 워커 프로세스)
하이브리드 접근 (권장)
@nestjs/schedule로 스케줄 정의 + BullMQ로 작업 큐잉:
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
@Injectable()
export class HybridSchedulerService {
constructor(@InjectQueue('tasks') private tasksQueue: Queue) {}
@Cron('0 0 * * *')
async scheduleDailyReports() {
// 스케줄링 로직과 작업 처리 분리
await this.tasksQueue.add('daily-report', {});
}
}이 접근법은 스케줄링 로직과 작업 처리를 분리하여 최고의 유연성을 제공합니다 (DEV Community (opens in a new tab)).
베스트 프랙티스
- 긴 작업은 큐 사용 - cron 핸들러에서 장시간 실행되는 작업 지양, 큐(예: Bull) 사용 (Medium (opens in a new tab))
Medium 아티클에서 "Avoid long-running tasks in cron handlers; use a job queue (e.g., Bull) if needed" 라고 명시되어 있습니다.
- 멱등성 유지 - 작업이 두 번 실행되어도 데이터가 손상되지 않도록 설계
"Keep jobs idempotent — if a job runs twice, it should not corrupt data" (Medium (opens in a new tab))
- 타임존 설정 - timeZone 옵션으로 사용자의 올바른 로컬 시간 존중
"By using the timeZone option, the job respects the correct local time for your users" (NestJS 공식 문서 (opens in a new tab))
-
에러 처리 - 모든 스케줄 작업에 적절한 에러 처리 및 로깅 포함
-
모니터링 - 프로덕션 환경에서는 작업 실행 여부를 모니터링 (Bull Board 등 활용)
참고 자료
- NestJS 공식 문서 - Task Scheduling (opens in a new tab)
- BullMQ NestJS Integration (opens in a new tab)
- Better Stack - Node.js Scheduler 비교 (opens in a new tab)
- LogRocket - Best Node.js Schedulers 비교 (opens in a new tab)
- DEV Community - Handling Cron Jobs with Bull (opens in a new tab)
- Medium - Mastering Background Cron Jobs in NestJS (opens in a new tab)