배경
기존의 PNU_Mail_Badara는 React 프론트엔드 + Nodejs 서버 + 이메일 전송 스케줄링을 오직 하나의 레포에서 담당했다. 아무래도 혼자서 개발하는 것이기도 했고, 빠른 디벨롭을 위해 굳이 레포를 나누어서 복잡성을 늘리기보다는 하나의 레포지토리에서 전부 관리하는 것이 편했다.
그러나 PNU_Mail_Badara 모놀리식 레포의 문제점은 "서버에서 하는 일이 너무 많다"는 것이었다.
1. React 프론트엔드 서빙
2. API 서버
3. 특정 시간마다 크롤링 + 이메일 전송 스케줄링
매일 특정 시간마다 스케줄링을 수행할 때, Koyeb(호스팅 서비스 플랫폼) 무료 플랜의 최대 메모리 사용량을 넘어가면서 배포 서버가 자꾸 셧다운되는 일이 발생했다.(참고: https://insengnewbie.tistory.com/477) 서버가 다시 켜지기까지는 대략 5분 정도의 빌드 시간이 소요된다. 현재 메일받아라의 구독자가 100명이 넘어가는 만큼, 빈번하게 서버가 죽는 일은 서비스 운영에 있어서 치명적인 단점이다. 그래서 최대한 서버의 부하를 줄이기 위해 레포를 분리하고, 기능별로 배포하기로 결정했다.
- React 프론트엔드: Github Pages 정적 배포
- Nodejs API 서버: Koyeb 배포
- 이메일 전송 스케줄링: Github Actions의 Cron 기능
React 정적 배포 in Github Page
- 기존: Nodejs 서버에서 정적 파일 서빙
- 현재: Github Pages로 정적 배포
github pages 배포에는 다음과 같은 장점이 있었다. 레포지토리 이름이 곧 "배포 URL"이 되기 때문에 UX 친화적인 URL을 생성할 수 있다! 게다가 mailbadara Organization에서 배포하면 더욱 알아보기 쉬운 URL이 된다!! 또한 Github Pages로 배포하는 것이기 때문에 Nodejs 서버의 부하를 확 낮출 수 있다.
https://mailbadara.github.io/homepage/
mailbadara Organization 개설
멤버가 오직 나 혼자인 mailbadara Organization을 개설했다. 배포 URL을 이쁘게 만들기 위함이기도 했지만, MailBadara 서비스는 KimCookieYa와는 완전히 독립적인 프로젝트임을 명확히 하고 싶었다.
Nodejs 서버와 스케줄링의 분리 with Github Actions
- 기존: Nodejs 서버에서 API 요청 처리와 매 시간마다 스케줄링 작업 수행
- 현재: Nodejs API 서버와 스케줄링 작업(Github Actions 이용)을 별도로 분리
MailBadara는 구독자들에게 매 시간마다 뉴스레터를 전송하는 서비스이다. 매 시간마다 특정 학과 홈페이지를 크롤링해서 해당 학과의 구독자들에게 데이터를 뉴스레터 형식으로 가공해서 전달한다. 매 시간마다 작업을 수행해야 했기에 서버에서 node-cron이라는 라이브러리를 사용해서 스케줄링을 구성했다. API 요청 처리와 Cron 스케줄링을 전부 처리하기에는 Koyeb의 무료 플랜의 메모리 제한량은 너무 연약했고... 서버가 자주 터졌다.
- 기존 서버 app.ts
const app = express();
const PORT = process.env.PORT || 8000;
// connect to database and set mock.
setMock();
app.use(cors());
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, "../../dist")));
// Endpoint: API Routing
app.use("/api/user", UserRouter);
app.use("/api/department", DepartmentRouter);
app.use("/api/email", EmailRouter);
app.use("/api/history", HistoryRouter);
// Endpoint: Client Routing
app.use("*", (__: Request, res: Response) => {
res.sendFile(path.join(__dirname, "../../dist", "index.html"));
});
app.listen(PORT, () => {
console.log("[Running] Server is running on port", PORT);
});
// cron job at 10:00-20:00 on Korea. 시차 9시간.
cron.schedule("0 1-12 * * 1-6", schedulingJobs);
그랬는데 얼마전 Github Acions의 cron 기능으로 작업을 자동화했다는 글을 읽었다. Github Actinos에서는 cron을 통해 레포지토리에서 특정 작업을 수행하도록 지정하는 것이 가능했던 것이다! 그렇다면 굳이 서버 자원을 낭비할 필요도 없이, Github Actions로 마이그레이션하기로 했다.
- 현재 newsletter-scheduler.ts
import { setMongoose, schedulingJobs } from "./src/job";
async function main() {
await setMongoose()
.then(() => schedulingJobs())
.catch((error) => console.error(error));
}
(async () => {
await main();
process.exit(0);
})();
- cron을 수행하는 Github Actions를 정의한 yml 설정 파일
name: Run schduling jobs for crawling.
on:
schedule:
- cron: "0 0-11 * * 1-6" # UTC 기준 월~금 00:00~11:00(한국 기준 09:00~20:00)
jobs:
run-script:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.0.27
# Github Reposiroty의 Secret 가져와서 .env 생성하기
- name: Create .env file
run: jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env
env:
SECRETS_CONTEXT: ${{ toJson(secrets) }}
- name: Install dependencies
run: bun install
- name: Run app.ts
run: bun scheduling
결론
스케줄링 작업이 아주 잘 동작하는 것을 확인할 수 있다. 이제 MailBadara 서비스는 Github가 망하지 않는한 영원히 동작할 것이다...! 완전한 자동화를 구축했다고 생각한다:) 서비스의 개선 여지는 많이 남아있지만, 당분간은 이 상태로 굴러가게 둘 것이다.
'프로젝트 > MailBadara' 카테고리의 다른 글
[토이프로젝트] MailBadara - (12) 메일바다라v3 (0) | 2024.08.14 |
---|---|
[토이프로젝트] MailBadara - (10) 모바일 구현 시작 (1) | 2024.02.14 |
[토이프로젝트] MailBadara - (9) 디자인 리뉴얼 (1) | 2024.01.11 |
[토이프로젝트] MailBadara - (8) 2차 서버 리팩토링 (1) | 2023.11.23 |
[토이프로젝트] MailBadara - (7) 1차 서버 리팩토링 (1) | 2023.11.22 |