체크리스트

임시 데이터베이스 적용을 위해 미리 결정한 체크리스트는 다음과 같습니다.

  1. 메모리에서의 데이터 관리: 삽입(insert)되는 데이터는 메모리에 모아두었다가 한 번에 DB에 bulk insert를 진행하기
  2. 데이터 갱신의 최적화: 데이터 갱신(update) 시 메모리에서 직접 갱신하며, 최종적으로 갱신된 내용만 DB에 update를 진행하기 빈번한 DB 접근을 줄이고 성능을 향상시키기
  3. 데이터 삭제 처리: 삭제(delete) 요청이 들어온 데이터가 메모리에 담겨져 있는 경우에는, 해당 데이터에 접근할 수 없도록 막기
  4. 서버 다운 대비: 서버가 다운됐을 때를 대비하여 fs.appendFile()을 활용하여 CSV 파일에 데이터를 저장하기 데이터를 DB에 전송한 후와 서버가 재시작됐을 때는 CSV 파일을 비우기
  5. Docker 환경 적용: 프로젝트에 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();
  }
  ...
}
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());
    });
  });
}
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));
    }
  });
}
@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);
  }
}