임시 데이터베이스 적용을 위해 미리 결정한 체크리스트는 다음과 같습니다.
insert)되는 데이터는 메모리에 모아두었다가 한 번에 DB에 bulk insert를 진행하기update) 시 메모리에서 직접 갱신하며, 최종적으로 갱신된 내용만 DB에 update를 진행하기
빈번한 DB 접근을 줄이고 성능을 향상시키기delete) 요청이 들어온 데이터가 메모리에 담겨져 있는 경우에는, 해당 데이터에 접근할 수 없도록 막기fs.appendFile()을 활용하여 CSV 파일에 데이터를 저장하기
데이터를 DB에 전송한 후와 서버가 재시작됐을 때는 CSV 파일을 비우기Docker를 적용하였기 때문에, CSV 파일은 Docker 컨테이너 내부가 아닌 Docker Volume을 활용하여 클라우드 서버에 저장하기임시 데이터베이스를 관리하기 위해, TemporaryDatabaseService 클래스를 구현하였습니다.
@Injectable()
export class TemporaryDatabaseService {
private database: Map<string, Map<string, Map<string, DataType>>> = new Map();
private readonly FOLDER_NAME = CSV_FOLDER;
constructor(
private readonly prismaMysql: PrismaServiceMySQL,
private readonly prismaMongoDB: PrismaServiceMongoDB,
) {
this.init();
}
async init() {
this.initializeDatabase();
await this.readDataFromFiles();
await this.executeBulkOperations();
}
...
}
init() 함수가 실행 됩니다. init() 함수에는 다음과 같은 함수들이 포함되어 있습니다.private initializeDatabase() {
const services = [
'USER_TB',
'PROFILE_TB',
'SPACE_TB',
'BoardCollection',
'PROFILE_SPACE_TB',
'REFRESH_TOKEN_TB',
'INVITE_CODE_TB',
];
const operations = ['insert', 'update', 'delete'];
services.forEach((service) => {
const serviceMap = new Map();
this.database.set(service, serviceMap);
operations.forEach((operation) => {
serviceMap.set(operation, new Map());
});
});
}
initializeDatabase 메서드를 통해 초기에 각 서비스별로 insert, update, delete 명령을 수행할 해시맵을 생성합니다. 이를 통해 메모리 상에서 빠른 데이터 접근을 가능하게 하였습니다.private async readDataFromFiles() {
const files = await fs.readdir(this.FOLDER_NAME);
return Promise.all(
files
.filter((file) => file.endsWith('.csv'))
.map((file) => this.readDataFromFile(file)),
);
}
private async readDataFromFile(file: string) {
const [service, commandWithExtension] = file.split('-');
const command = commandWithExtension.replace('.csv', '');
const fileData = await fs.readFile(join(this.FOLDER_NAME, file), 'utf8');
fileData.split('\\n').forEach((line) => {
if (line.trim() !== '') {
const [uniqueKey, ...dataParts] = line.split(',');
const data = dataParts.join(',');
this.database
.get(service)
.get(command)
.set(uniqueKey, JSON.parse(data));
}
});
}
readDataFromFiles 및 readDataFromFile 메서드를 통해 CSV 파일로부터 데이터를 읽어와 해시맵에 저장합니다.@Cron('0 */10 * * * *')
async executeBulkOperations() {
for (const service of this.database.keys()) {
const serviceMap = this.database.get(service);
const prisma =
service === 'BoardCollection' ? this.prismaMongoDB : this.prismaMysql;
await this.performInsert(service, serviceMap.get('insert'), prisma);
await this.performUpdate(service, serviceMap.get('update'), prisma);
await this.performDelete(service, serviceMap.get('delete'), prisma);
}
}