도입
node-schedule은 서버단에서 스케줄링을 도와주는 라이브러리이다.
필자가 프로젝트에서 node-schedule를 사용한 이유는 반복 작업이 아닌, 일정 기간 이후에 db의 값을 바꾸는 작업을 하기 위해서였다. 반복 작업은 nest/schedule 등 더 좋은 라이브러리가 있다. 한편, crontab을 사용할 수 있으나 아직 배포 단계에서 확인이 어려운 수준이라 서버 단계에서의 schedule을 해낼 라이브러리를 찾아보았다.
스케줄링하는 라이브러리를 비교한 3년 전 글을 첨부한다. 아래 글의 저자와 같이 최근 사용되는 정도를 비교하였을 때 node-schedule을 사용하는 편이 낫다고 판단하였다.
https://velog.io/@filoscoder/%EC%8A%A4%EC%BC%80%EC%A4%84-%EC%97%85%EB%AC%B4-%EC%9E%90%EB%8F%99%ED%99%94-Node-cron-vs-Node-schedule-%EB%B9%84%EA%B5%90-clk4dyynve
agenda 또는 bree의 경우 라는 라이브러리의 경우 서버 종료 후 재시작해도 schedule을 유지한다는 장점이 있다. 하지만 각각 사용하는 db가 정해져 있어 백엔드 db 서버가 무엇이냐에 따라 백엔드의 유연함이 덜어질 수 있으며, db에 저장하고자 하는 schedule의 값에 따라 차라리 node-schedule에서 임의로 db에 값을 저장하는 편이 나을 수 있다.
따라서 필자는 사용하고 연구할 라이브러리를 node-schedule로 설정하였다.
필자가 node-schedule에서 주로 사용한 기능은 특정 date에 job이 실행되는 기능이다.
목표
1. node-schedule은 서버 앱이 종료되면 등록된 scheduleJob이 사라진다. 어떠한 에러로든 서버가 종료되는 경우에, 서버를 다시 시작하면 schedule을 유지될 수 있도록 schedulejob과 관련된 정보를 db에 넣고자 한다.
2. 또한 scheduleJob이 어떻게 형성되는지 내부 코드를 들여다보고자 한다.
탐색
1. scheduleJob
생성된 scheduleJob 객체를 확인해보자. 프로젝트 코드의 일부를 첨부한다.
async addIssue(issueData: AddIssueDto): Promise<boolean> {
const instance = await new this.issueModel(issueData);
const save = await instance.save();
const week = 7 * 24 * 60 * 60 * 1000;
const dueDate = new Date(Date.now() + week);
const setRegiStatusInactiveJob = scheduleJob(dueDate, () => {
this.setRegiStatus(save._id, 'expired');
});
console.log(setRegiStatusInactiveJob);
}
<ref *1> Job {
pendingInvocations: [
Invocation {
job: [Circular *1],
fireDate: [CronDate],
endDate: undefined,
recurrenceRule: [RecurrenceRule],
timerID: [Timeout]
}
],
job: [Function (anonymous)],
callback: false,
running: 0,
name: '<Anonymous Job 1 2022-08-30T06:03:43.464Z>',
trackInvocation: [Function (anonymous)],
stopTrackingInvocation: [Function (anonymous)],
triggeredJobs: [Function (anonymous)],
setTriggeredJobs: [Function (anonymous)],
deleteFromSchedule: [Function (anonymous)],
cancel: [Function (anonymous)],
cancelNext: [Function (anonymous)],
reschedule: [Function (anonymous)],
nextInvocation: [Function (anonymous)],
isOneTimeJob: true
}
해당 객체를 바로 db에 넣기에는 전처리 작업이 필요하다. 가장 필요한 작업은 이 schedule의 이름을 설정하는 것이다.
scheduleJob 생성자의 parameter로 이름을 설정할 수 있는 param은 없는지, cronDate 값은 scheduleJob 객체의 어디에 반영이 되는지 궁금해졌다.
2. scheduleJob의 이름을 설정하기
node-schedule의 npm 홈페이지에는 설명이 없고, 운영이 활성화되어 있지 않아 직접 코드를 뜯어보고자 한다.
//node_modules/node-schedule/lib/Job.js
function resolveAnonJobName() {
const now = new Date()
if (anonJobCounter === Number.MAX_SAFE_INTEGER) {
anonJobCounter = 0
}
anonJobCounter++
return `<Anonymous Job ${anonJobCounter} ${now.toISOString()}>`
}
function Job(name, job, callback) {
// setup a private pendingInvocations variable
this.pendingInvocations = [];
//setup a private number of invocations variable
let triggeredJobs = 0;
// Set scope vars
const jobName = name && typeof name === 'string' ? name : resolveAnonJobName();
this.job = name && typeof name === 'function' ? name : job;
// Make sure callback is actually a callback
if (this.job === name) {
// Name wasn't provided and maybe a callback is there
this.callback = typeof job === 'function' ? job : false;
} else {
// Name was provided, and maybe a callback is there
this.callback = typeof callback === 'function' ? callback : false;
}
// 이하 중략
// define properties
Object.defineProperty(this, 'name', {
value: jobName,
writable: false,
enumerable: true
});
// 이하 생략
}
변수는 총 3개를 받으며, parameter 이름이 name인 것으로 보아 해당 변수 자리에 'string'을 넣으면 이름을 설정할 수 있다. 하지만 npm 홈페이지에서 설명한대로 입력된 값은 'Date' 객체이기에 resolveAnonJobName을 통해 임시 이름이 설정되는 것이다.
그럼 cron date 값은 어떻게 넘겨야 하는가?라는 의문점 발생한다.
한편, Object.defineProperty에서 writable이 false인 것으로 보아 scheduleJob의 name은 readonly 상태일 것이다. 아니나 다를까, 다음과 같은 작업을 진행하면 오류가 발생한다.
setRegiStatusInactiveJob.name = '이름 바꿔주세요.'
// TypeError: Cannot assign to read only property 'name' of object '#<Job>'
그럼 이름을 수정할 방법은 정말 없는 것인가?
객체를 새롭게 생성하면 된다. (참고: https://kir93.tistory.com/entry/readonly-%EA%B0%9D%EC%B2%B4-%EC%88%98%EC%A0%95%ED%95%98%EA%B8%B0)
async addIssue(issueData: AddIssueDto): Promise<boolean> {
const instance = await new this.issueModel(issueData);
const save = await instance.save();
const week = 7 * 24 * 60 * 60 * 1000;
const dueDate = new Date(Date.now() + week);
const setRegiStatusInactiveJob = scheduleJob(dueDate, () => {
this.setRegiStatus(save._id, 'expired');
});
const newObject = { ...setRegiStatusInactiveJob };
// issueDate는 request.body에 해당한다.
newObject.name = issueData.title;
console.log(newObject);
}
{
pendingInvocations: [
Invocation {
job: [Job],
fireDate: [CronDate],
endDate: undefined,
recurrenceRule: [RecurrenceRule],
timerID: [Timeout]
}
],
job: [Function (anonymous)],
callback: false,
running: 0,
name: 'cronjob',
trackInvocation: [Function (anonymous)],
stopTrackingInvocation: [Function (anonymous)],
triggeredJobs: [Function (anonymous)],
setTriggeredJobs: [Function (anonymous)],
deleteFromSchedule: [Function (anonymous)],
cancel: [Function (anonymous)],
cancelNext: [Function (anonymous)],
reschedule: [Function (anonymous)],
nextInvocation: [Function (anonymous)],
isOneTimeJob: true
}
console.log의 결과값을 통해 볼 수 있듯, request로 넘어온 string 값이 scheduleJob.name에 들어간 것을 볼 수 있다. 또한 cronDate 값도 잘 복사됨을 확인할 수 있다.
console.log(newObject.pendingInvocations[0].fireDate);
// CronDate {
_date: DateTime {
ts: 1662446135074,
_zone: LocalZone {},
loc: Locale {
locale: 'ko-KR',
numberingSystem: null,
outputCalendar: null,
intl: 'ko-KR',
weekdaysCache: [Object],
monthsCache: [Object],
meridiemCache: null,
eraCache: {},
specifiedLocale: null,
fastNumbersCached: null
},
invalid: null,
weekData: null,
c: {
year: 2022,
month: 9,
day: 6,
hour: 15,
minute: 35,
second: 35,
millisecond: 74
},
o: 540,
isLuxonDateTime: true
}
}
3. scheduleJob의 cron date를 넣기. (연구중)
하지만 위의 객체는 근본적으로 scheduleJob이라고 할 수 없기 때문에, 직접 scheduleJob에 name과 cron date를 넣을 수 있는 방법을 찾고자 한다.
'Web' 카테고리의 다른 글
[nest] jest cannot find moudle error (0) | 2022.09.06 |
---|---|
[Nest] Internal server error (0) | 2022.08.25 |
[Express] body parser? urlencoded? (0) | 2022.08.24 |
[CORS] CSR에서 google oauth2.0 적용기 (0) | 2022.07.29 |
[Express] request 객체에 object 담기 (0) | 2022.07.01 |