[Nest.js]17. DB 설계 갈아엎기..

김지엽·2023년 11월 29일
0
post-thumbnail
post-custom-banner

1. 개요

부트캠프에서 프로젝트를 통해 튜터님들께 피드백을 받으면서 개인 프로젝트의 설계에 대해서도 몇가지 잘못된 점과 추가할 테이블이 있어서 수정하기로 했다.

2. 잘못된 점

- 배열의 저장

기존 방식
배열을 JSON을 통해 문자열 형태로 변환 후 문자열 형태로 데이터를 저장

변경 후
새로운 테이블을 생성해 A와 B 모델을 N:M 관계 설정

- 사용하지 않는 컬럼 삭제

특정 컬럼을 사용하지 않아서 삭제하기로 했다.

- 잘못된 primary key

예전에는 pk에 대한 이해가 부족해서 pk를 생성하고 다른 unique한 컬럼을 생성해 그것을 pk처럼 다루었다.

기존 방식
A 테이블
PK-id-int
Field-webtoonId-string

변경 후
A 테이블
PK-id-string

3. 추가할 점

웹툰에 사용자가 댓글을 남길수 있도록 댓글 테이블을 추가해야한다. 댓글 테이블은 사용자와 웹툰 테이블과 각각 1:N 관계를 가진다.

4. ERD 설계

ERD CLOUD

5. 데이터 백업

현재 DB의 테이블에는 이미 많은 정보가 들어 있으며 그 테이블을 수정하는 작업이기 때문에 잘못하면 데이터의 손실이 발생할 수 있다. 따라서 손실되더라도 바로 복구가 가능하도록 데이터를 미리 백업한다.

- AWS RDS 수동 백업(1)

RDS는 자동으로 백업 기능을 지원하지만, 정확히 현재 상태를 백업으로 지정해주고 싶기 때문에 수동으로 진행한다.

AWS RDS -> 스냅샷 -> 스냅샷 생성

백업을 하고 싶다면 생성된 스냅샷을 들어가 스냅샷 복원을 선택하면 된다.

- AWS RDS 자동 백업

만약 스냅샷을 지정하는 것이 아니라 특정 시간대로 돌아가고 싶다고 한다면 자동 백업을 한다.

AWS RDS -> 자동 백업

6. Sequelize migration 파일 작성

다른 모델들은 저장되어 있는 데이터가 없어서 sequelize.sync를 사용하면 되고, 현재 마이그레이션을 사용해 변경해야 하는 것은 user와 webtoon 테이블이다.

- user

'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up (queryInterface, Sequelize) {
    return Promise.all([
      // 1. userId -> email로 변경
      queryInterface.renameColumn("user", "userId", "email"),
    ]);
  },

  // up을 반대로
  async down (queryInterface, Sequelize) {
    return Promise.all([
      queryInterface.renameColumn("user", "email", "userId"),
    ]);
  }
};

- webtoon

'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up (queryInterface, Sequelize) {
    return Promise.all([
      // 기존의 id 컬럼 삭제
      await queryInterface.removeColumn("webtoon", "id"),
	  // webtoonId 컬럼을 pk로 설정
      await queryInterface.addConstraint("webtoon", {
        fields: ["webtoonId"],
        type: "primary key",
        name: "webtoon_webtoonId_key",
      }),
	  // webtoonId -> id 이름 변경
      await queryInterface.renameColumn("webtoon", "webtoonId", "id"),
	  // 필요없는 컬럼 삭제
      queryInterface.removeColumn("webtoon", "embVectorDescription")
    ]);
  },

  // up을 반대로
  async down (queryInterface, Sequelize) {
    return Promise.all([
      await queryInterface.renameColumn("webtoon", "id", "webtoonId"),
      
      await queryInterface.removeConstraint("webtoon", "webtoon_webtoonId_key"),

      await queryInterface.addColumn("webtoon", "id", {
        type: Sequelize.INTEGER,
        allowNull: false,
        primaryKey: true,
        autoIncrement: true,
      }),

      queryInterface.addColumn("webtoon", "embVectorDescription", {
          type: Sequelize.TEXT, 
          allowNull: true, 
      }),
    ]);
  }
};

- migrantion 실행

$ npx sequelize db:migrate

특정 migration 파일만 실행하고 싶다면 --to 옵션을 사용할수 있습니다.
ex) $ npx sequelize db:migrate --to 20231201-userConfig.js

7. 기존의 장르 배열 옮기기

기존의 장르 목록은 배열을 JSON을 통해 문자열 형태로 변환시켜 다음과 같은 형태로 저장되어 있다.

'["힐링물","짝사랑","소꿉친구","현대로맨스","로맨틱코미디","사차원","친구>연인","삼각관계","친구"]'

이제 genrewebtoon 테이블을 통한 웹툰과 장르의 N:M관계를 설정해 놓았으니 저 데이터를 통해 관계 테이블에 데이터를 저장시켜야 한다.

쿼리로 작성하기에는 내 실력이 모자라 코드를 통해 작성하였다.

async test(): Promise<void> {
    try {
        const webtoons = await this.webtoonService.getAllWebtoon();

        for (let webtoon of webtoons) {
            const keywords = JSON.parse(webtoon.genres);

            for (let keyword of keywords) {
                const genre = await this.getGenre({ keyword, service: "kakao" });
                if (!genre) {
                    console.log(keyword, "장르가 존재하지 않습니다.");
                    continue;
                }

                const genreId = genre.id;
                const webtoonId = webtoon.id;

                const isExist = await this.genreWebtoonModel.findOne({
                    where: { genreId, webtoonId }
                });

                if (isExist) {
                    console.log(`이미 존재합니다.\n[${webtoonId}, ${genreId}]`);
                    continue;
                }

                await this.genreWebtoonModel.create({
                    genreId: genre.id,
                    webtoonId: webtoon.id,
                });

                console.log(`\n\n[생성]\n웹툰id: ${webtoon.id}\n제목: ${webtoon.title}\n장르키워드: ${genre.keyword}`);
            }
        }
    } catch(e) {
        console.log(e);
    }
}

참고

sequelize-typescript
sequelize-migration
aws rds 백업

profile
욕심 많은 개발자
post-custom-banner

0개의 댓글