이번 사이드프젝트를 경험하면서 배워야할 것들도 많고 힘들기도(자괴감)하고 어려웠지만 새로운 경험들도 할 수 있는게 너무 좋았다.
내용의 순서는 이렇다
- DB 데이터를 옮긴다.
- nestJS의 라이브러리를 사용해서 batch 작업을 수행한다.
그럼 시작해보자
상황
- table1, table2가 있다.
- table 1의 2개의 column을 table 2에 옮긴다.(옮길때 table2를 일부가공하는 과정도 있었다.)
- 사전에 table1에서 2로 옮긴 데이터는 변경사항이 있을 수도 있으니 update쿼리를 수행하게 하고 새로운 데이터가 생기는 경우에는 insert 쿼리를 수행할 수 있도록 하게 한다.
//table1에서 table2로 옮기기
async generateFuction(){
//1. 데이터 추출(예시코드이기 때문에 실제로 사용하고자하는 코드에서는 다듬기가 필요하다.)
const table1Datas= query(`select distinct column1, column2 from table1 `)
const table2Datas= query(`select distinct column1, column2 from table2`)
//아래 절차를 수행하기 위해 table2의 가공이 필요(데이터의 요소 추출)
const table2List=table2Datas.map(el=>el.column1)
//2. table1과 2가 서로 매칭되는지 확인
//table1Datas에서 추출한 데이터를 반복문 배열로 추출
for(const[table1Emelents] of table1Datas){
//taeble1에서 추출한 요소들이 table2Datas에 들어있는지 확인하고 true이면 update쿼리 실행
if(table2List.includes(table1Elements['table1EmelentColumn1']){
const returnItem={...table1Elements}
//returnItem의 가공 시작
returnItem['~~~']=returnItem['table1ElementColumn1'].sort().reverse()...
//계속 가공
.
.
.
}
// 가공이 끝난 값을 update 쿼리로 DB에 반영시키기
await this.yourRepository.query(
`
UPDATE table2 SET column1, column2 WHERE column3=?
`
),[parameter1, parameter2, parameter3]
}else{
// table1에서 추출한 요소들이 table2에 없으면 없는 경우에는 insert 쿼리 실행
returnItem={...table2Elements}
returnItem['~~~~~']=returnItem['table1ElementColumn1'].sort().reverse()
//계속 가공
.
.
.
await this.yourRepository.query(
`
INSERT INTO table2(column1, column2, column3, column4)
VALUES(?,?,?,?)
`,[parameter1, parameter2, parameter3, parameter4]
)
}
}
위의 방식에서는 한가지 변수에 여러 데이터를 포함할 수 있기 때문에 구조분해할당
도 사용하게 되었다. 구조분해할당을 잘 사용할 수 있는 아주 좋은 예시였다고 생각한다.
구조분해 할당에 대해서는 따로 작성한 구조분해할당블로그 기초내용을 참고하면 좋을 것 같다.
이런방식의 코드를 예시로하면 table1, table2에 대한 DB를 이동할 수 있는 기반을 마련할 수 있다. 이렇게 작업하는 기능에 대해서는 완성했다고하면
그다음 작업은 이 기능을 스케줄링을 통해서 정기적으로 기능을 수행할 수 있게 하는 기능이 필요했다.
그래서 나온것이 batch!
일단 batch에 대한 간단한 설명을 하자면 '데이터를 실시간으로 처리하는것이 아닌 원하는 시점에 한번에 (대량의)데이터를 처리할 수 있도록 하는 있는 기능(시스템)' 이다.
이런 시스템을 사용하는 경우의 예시를 들자면
1. 금융 시스템(ex.은행)의 정산
2. 커머스 프로그램의 대규모 변경사항의 일괄 진행
등
등
등..
을 예로 들 수 있다.
나는 이 배치를 왜 활용하게 됐냐면 위 1번 DB 옮기는 작업이 수시로 업데이트 될 수 있기 때문에 특정 시간대에 업데이트가 될 수 있는 기능이 필요하다고 생각했다.
그리고 나는 nestJS
로 프로젝트를 진행하고 있었기 때문에 batch를 보다 편하게 할 수 있는 방법이 있었다.
공식문서에서도 이에 대한 방법이 있다.공식문서 링크
이런 기능들에 대해서는 scheduling
이라고 표현하는 것 같다.
nestJS
에서는 @nestjs/schedule
이라는 라이브러리를 활용하고, Cron
이라는 데코레이터를 활용한다.
$ npm install --save @nestjs/schedule
해당 라이브러리를 설치하고 활용하면 된다.
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [
ScheduleModule.forRoot()
],
})
export class AppModule {}
module 데코레이터를 활용해서 schedule 모듈을 import를 해서 사용할 수 있도록 한다.
그리고 모듈을 불러와서 기능을 활용하도록 코딩하자면 방법에 따라 작성해볼 수 있다.
//file.scheduler.ts
//type 1
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import {YourService} from './yourService.ts'
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name)
constructor(private yourService:YourService){}
@Cron('45 * * * * *')
async handleCron() {
this.logger.debug('Called when the current second is 45');
await yourService.function();
}
}
//type2
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import {YourService} from './yourService.ts'
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
constructor(private yourService:YourService){}
@Cron(CronExpression.EVERY_30_SECONDS)
handleCron() {
this.logger.debug('Called every 30 seconds');
await yourService.function();
}
}
이렇게 준비하고 서버를 작동시키면 스케줄러에 따라서 설정한 시점에 사용하고자 하는 기능을 포함시키면 batch를 사용할 수 있게 된다.
작성하는 방법은 여러방법이 있으니 공식문서를 참고하고 다른 블로그들을 참고해서 본인만의 방식으로 batch를 만드는 게 좋을 것 같다!
추가참고 블로그 링크
1. https://velog.io/@jojoo/NestJS-Batch-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-with-cron