[ 01 개념 알아보기 ]
(1) cron이란?
- 유닉스 계열 컴퓨터 운영체제(Unix, Linux 등)의 잡 스케줄러이다. 잡 스케줄러란 어떤 작업을 특정 시간에 실행시키기 위한 데몬이다.
- 일정한 시간 간격으로 수행되어야 할 작업이나 관리자가 그 시간에 작업을 할 수 없는 상황일 때에도 서버는 항상 돌아가고 있다는 점을 이용하는 아주 유용한 방법이다.
- cron은 셸 명령어들이 주어진 일정에 주기적으로 실행하도록 규정해놓은 crontab (cron table) 파일에 의해 구동된다.
- 셸 명령어들로 필요한 작업을 프로그래밍한 파일이 shell script(.sh)이다.
- 윈도우에서는 작업 스케줄러(Task Scheduler)와 배치(.bat) 파일로 대체할 수 있다.
(2) node-schedule를 선택한 배경
- 우리는 Ubuntu 서버를 사용하여 배포할 예정이었기 때문에 처음에는 crontab을 사용하려고 했으나, 찾아보니 이러한 스케줄링 기능을 운영체제 상관없이 + 코드로 제어하며 사용할 수 있도록 도와주는 패키지들이 많이 있었다.
- 비교해보니 크게 DB를 사용하는 것, DB를 사용하지 않는 것으로 나뉘고 DB 종류도 redis와 MongoDB로 나뉘었다.
- 그런데 redis는 스케줄러 작업의 DB로 사용하기에 단점이 있다고 했고(설정 필요, 데이터 무결성 보장 문제 등) 그래서 MongoDB를 이용하는 agenda와 같은 패키지가 등장한 것 같았다.
- 하지만 우리 프로젝트에서는 메인 DB로 MySQL을 사용하고 있었고, 따라서 굳이 MongoDB를 추가해야 하는 agenda도 탈락, 남은 것 중에 가장 cron job을 구체적으로 프로그래밍 할 수 있다는 node-schedule을 선택했다.
참고 : https://blog.logrocket.com/comparing-best-node-js-schedulers/
[ 02 node-schedule ]
(1) 패키지 설치
- 사용하기 위해서는 먼저 패키지 설치부터 진행한다. (공식문서)
npm i node-schedule
(2) job 추가
- 나는 앞서 back end 서버와 push 서버를 분리했기 때문에, node-schedule은 자동으로 푸시 알림이 가도록 필요한 push 서버에서 작업했다.
- 시간에 따라 영양제 일정 푸시 알림이 가도록 하는 api는 이미 작성한 상태이므로, 이걸 30분마다 자동으로 실행하도록 하는 job을 추가한다.
- 먼저 api 요청을 보낼 함수를 정의해줬다.
// 배포하면 주소 변경해야 함
const serverUrl = "http://localhost:5004/";
const push = async (time: Date) => {
try {
await axios
.get(serverUrl + "subscribe/push-supplements", {
params: { time: time },
})
.then((res) => {
const pushResultObj = {
status: res.status,
statusText: res.statusText,
data: res.data,
};
});
} catch (error: any) {
console.log(error)
};
- 그리고 이 함수를 실행하는 job을 설정하는 부분이다.
- 직접 crontab 룰을 사용해서 추가도 가능하지만 RecurrenceRule( ) 메소드를 사용해야 timezone 설정이 가능했다.
- 스케줄러가 오전 6시부터 밤 12시까지를 보여주기 때문에 그 시간 내에 30분 간격으로 동작하도록 설정해줬다.
- 현재 시간을 인자로 보내줘야 해서 new Date()를 사용했다. DB 쿼리가 정상적으로 조회되기 위해서는 시간이 00초로 끝나야 하지만 작동 시간에 딜레이가 생길까봐 걱정했는데 문제없이 00초로 요청이 갔다.
const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = new schedule.Range(0, 6); // 매일
rule.hour = [0, new schedule.Range(6, 23)]; // 오전 6시부터 자정까지
rule.minute = [0, 30]; // 정각과 30분에
rule.tz = "Asia/Seoul"; // 한국 시간 기준
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const job = schedule.scheduleJob(rule, () => {
const current = new Date();
push(current);
});
- 전체 코드는 다음과 같다.
더보기
utils / webPush.ts
- 로그를 추가하는 부분은 다음 포스팅에서 설명하겠다.
import axios from "axios";
import webpush from "web-push";
import schedule from "node-schedule";
import moment from "moment";
import "moment-timezone";
import { logger } from "../utils/winston";
import { emailUtil } from "../utils/emailUtil";
moment.tz.setDefault("Asia/Seoul"); // 로그 시간대 한국 기준으로 변경
// VAPID keys should only be generated only once.
const vapidKeys = {
publicKey: <any>process.env.PUBLIC_KEY,
privateKey: <any>process.env.PRIVATE_KEY,
};
export default (): void => {
webpush.setVapidDetails(
"mailto:s0n9h2@gmail.com",
vapidKeys.publicKey,
vapidKeys.privateKey
);
};
// 배포하면 주소 변경해야 함
// const serverUrl = "http://localhost:" + process.env.PORT + "/";
const serverUrl = "http://localhost:5004/";
const push = async (time: Date) => {
try {
await axios
.get(serverUrl + "subscribe/push-supplements", {
params: { time: time },
})
.then((res) => {
const pushResultObj = {
status: res.status,
statusText: res.statusText,
data: res.data,
};
// 푸시 알림 발송 성공하면 push.log에 기록
if (res.status == 200) {
logger.info(`${time}`, pushResultObj);
console.log(`${time}에 설정된 푸시 알림을 전송했습니다.`);
}
});
} catch (error: any) {
const pushErrorObj = {
syscall: error.syscall,
code: error.code,
errno: error.errno,
};
// 푸시 알림 발송 실패하면 error.log에 기록
logger.error(`${time}`, pushErrorObj);
console.log(`${time}에 설정된 푸시 알림 전송에 실패했습니다.`);
const data = {
developers: ["s0n9h2@gmail.com", "tbr06057@naver.com"],
time: time,
errorContent: pushErrorObj,
};
// 담당 개발자들에게 이메일 전송
emailUtil.pushErrorEmail(data);
}
};
const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = new schedule.Range(0, 6); // 매일
rule.hour = [0, new schedule.Range(6, 23)]; // 오전 6시부터 자정까지
rule.minute = [0, 30]; // 정각과 30분에
rule.tz = "Asia/Seoul"; // 한국 시간 기준
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const job = schedule.scheduleJob(rule, () => {
const current = new Date();
push(current);
});
[ 03 동작하는 모습 ]
- 오전 11시 영양제 일정 추가
- 11시가 되면 푸시 알림이 전송되는 모습
'엘리스 AI트랙 4기 > 프로젝트' 카테고리의 다른 글
[PWA] Push Notification 외전1. query string으로 사용자 인증값 전달하기 (feat. AES 암호화) (0) | 2022.07.27 |
---|---|
[PWA] Push Notification 안드로이드 QR 코드로 구독하기 (0) | 2022.07.25 |
[PWA] Push Notification 상황별 다른 알림 옵션 설정하기 (0) | 2022.07.16 |
[PWA] Push Notification Nodejs 프로젝트에 적용하기 (0) | 2022.07.15 |
[PWA] Push Notification 튜토리얼 따라해보기 (0) | 2022.07.11 |