DB 마이그레이션 전략 변경

Collin·2022년 11월 15일
0

[변경전] typeorm sync


  • 단점
  1. 현재 백앤드의 entity따라서 db와 동기화되는 typeorm의 sync 옵션을 사용했는데
    해당 기능의 문제는 db 컬럼명 수정을 하더라도 변경이 아닌 삭제후 생성으로 기존의 데이터가 유실됨

  2. 데이터가 존재하는 경우 db 마이그레이션시 db error 발생 ⇒ 관련 데이터 다 지우고 스키마 마이그레이션 진행


  • 장점
  1. 빠른 개발 속도

💡   운영(라이브/프로덕션)환경 전까지는 데이터 유실보다 속도가 중요해 잘 사용했지만

   운영환경으로 올리게 됨으로써 전략 수정이 필요



[변경후] typeorm migration cli


  • 단점
  1. 개발추가 공수 (migration 파일 관리를 철저히 해야 한다)

  2. 여전히 데이터가 존재하는 경우 db 마이그레이션시 뻗음

  • 장점
  1. 해당 기능은 컬럼명 변경시 의도한대로 컬럼명만 변경된다

  • 보완사항

   단점의 1번은 안전성과 트레이드 오프 관계라 어쩔 수 없지만 2번은 필수적으로 해결해야 할 부분

   [단점2의 보완사항] - 생성된 마이그레이션 코드를 직접 수정


# CASE) db 구조 변경으로 fk 관련한 수정 사항 발생

grand, parent, child 테이블 존재

grand - parent = 1:N
parent - child = 1:1 과 같은 관계에서

grand - parent = 1:N
grand - child = 1:1 과 같은 관계로 db 구조 변경

즉 child의 부모 테이블이 parent -> grand 로 변경되는 경우

[수정전] db 스키마 마이그레이션 코드만 존재

import { MigrationInterface, QueryRunner } from "typeorm";

export class fkTest1667979162145 implements MigrationInterface {
    name = 'fkTest1667979162145'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_1e32750d2d74bb369f3894c379b\``);
        await queryRunner.query(`DROP INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\``);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`parent_id\` \`grand_id\` int NOT NULL`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD UNIQUE INDEX \`IDX_ca31559c5ebea54d722bdbdc92\` (\`grand_id\`)`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\` (\`grand_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_ca31559c5ebea54d722bdbdc927\` FOREIGN KEY (\`grand_id\`) REFERENCES \`grand\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_ca31559c5ebea54d722bdbdc927\``);
        await queryRunner.query(`DROP INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\``);
        await queryRunner.query(`ALTER TABLE \`child\` DROP INDEX \`IDX_ca31559c5ebea54d722bdbdc92\``);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`grand_id\` \`parent_id\` int NOT NULL`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\` (\`parent_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_1e32750d2d74bb369f3894c379b\` FOREIGN KEY (\`parent_id\`) REFERENCES \`parent\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
    }
}

[수정 후] FK, Index 와 같은 제약사항이 삭제된 이후 데이터 마이그레이션 코드를 삽입

import { MigrationInterface, QueryRunner } from "typeorm";

export class fkTest1667979162145 implements MigrationInterface {
    name = 'fkTest1667979162145'

    private async holdNewData(queryRunner: QueryRunner){
        const result = await queryRunner.query(
            `SELECT 
                child.id, 
                child.parent_id, 
                parent.grand_id 
            FROM 
                child
            JOIN
                parent
            ON 
                parent.id = child.parent_id`
        );
        console.log(result)
        return result;
    }

    private async saveNewData(queryRunner: QueryRunner, newData){
        await Promise.all(newData.map(async nd => await queryRunner.query(
            `UPDATE child SET grand_id = ${nd.grand_id} WHERE id = ${nd.id}`
        )));
    }

    public async up(queryRunner: QueryRunner): Promise<void> {
        const newData = await this.holdNewData(queryRunner);
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_1e32750d2d74bb369f3894c379b\``);
        await queryRunner.query(`DROP INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\``);
        await this.saveNewData(queryRunner, newData);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`parent_id\` \`grand_id\` int NOT NULL`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD UNIQUE INDEX \`IDX_ca31559c5ebea54d722bdbdc92\` (\`grand_id\`)`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\` (\`grand_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_ca31559c5ebea54d722bdbdc927\` FOREIGN KEY (\`grand_id\`) REFERENCES \`grand\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
    }


    public async holdPrevData(queryRunner: QueryRunner){
			//up에서 동작하는 함수와 반대 기능
    }
    public async savePrevData(queryRunner: QueryRunner, newData){
			//up에서 동작하는 함수와 반대 기능
    }
    public async down(queryRunner: QueryRunner): Promise<void> {
        const newData = await this.holdPrevData(queryRunner);
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_ca31559c5ebea54d722bdbdc927\``);
        await queryRunner.query(`DROP INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\``);
		await this.savePrevData(queryRunner, newData);
        await queryRunner.query(`ALTER TABLE \`child\` DROP INDEX \`IDX_ca31559c5ebea54d722bdbdc92\``);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`grand_id\` \`parent_id\` int NOT NULL`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\` (\`parent_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_1e32750d2d74bb369f3894c379b\` FOREIGN KEY (\`parent_id\`) REFERENCES \`parent\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
        
    }

}
profile
세상에 선한 영향력을 끼치는 서비스를 개발하고 싶은 개발자입니다

0개의 댓글