들어가며

그동안 프로젝트를 진행하며 마이그레이션에 대해 모른 채 진행하다 보니 주먹구구 식으로 테이블에 대한 변경사항을 업데이트함으로 인해 수많은 사이드 이펙트가 발생했습니다.

예를 들어 현재 테이블의 상태만 파악 가능하고, 어떤 컬럼이 어떤 데이터 형식에서 어떤 데이터 형식으로 바뀌었는지 추적이 불가능하다는 것이었습니다. 뿐만 아니라 TypeORM 의 sync 옵션을 true 로 설정해놓고 개발을 진행했던 당시 테이블에 저장되어 있던 데이터가 모두 날아가버리는 불상사가 발생했던 적도 있었습니다.

이와 같은 상황이 다시는 일어나지 않도록 해결책을 찾던 도중 마이그레이션에 대해 알게 되었고, 이에 대해 공부한 내용을 정리해 게시글로 남겨놉니다.

이 게시글은 필자가 여러 블로그 및 공식 문서를 통해 정리한 내용이기 때문에 오류가 있을 수 있습니다.  

마이그레이션이란?

마이그레이션은 바로 데이터베이스의 이력을 관리하는 프로세스입니다.

우리가 작성한 코드를 git 을 통해 버전 관리하는 것과 같이 데이터베이스 또한 migration 을 통하여 버전을 관리합니다.

보통 변경할 사항에 대하여 변경되는 query와 해당 변경사항을 롤백시킬 수 있는 query를 작성하게 됩니다.

 

TypeORM 에는 synchronize 라는 옵션이 존재하며, 이 옵션을 true 로 설정 시 우리가 만든 엔티티들을 데이터베이스의 테이블로 변환해줍니다. synchronize: true 로 설정된 상태에서 엔티티에 대한 변경사항이 생길 경우 이를 즉시 반영하는데 이 과정에서 기존에 저장되어 있던 모든 데이터들이 삭제될 위험이 있습니다.

따라서 처음에만 synchronize: true 로 설정해 엔티티에 상응하는 테이블들을 생성하고, 그 이후에는 false 로 설정해줍니다. 

특히 production 레벨의 어플리케이션에서는 synchronize 옵션을 false 로 두고 마이그레이션 전략을 가집니다.

TypeORM 에서 마이그레이션 적용 과정

1. typeORM 설정 파일(dataSource.ts) 작성하기

2. package.json 에서 script 작성하기

3. 마이그레이션 파일 생성 및 query 작성하기

4. 마이그레이션 명령 실행하기

typeORM 설정 파일(dataSource.ts) 작성하기

TypeORM의 Connection 정보를 정의하는 dataSource.ts 파일을 작성합니다.

dataSource.ts

import { DataSource } from 'typeorm';
import dotenv from 'dotenv';
import { ChannelChats } from './src/entities/ChannelChats';
import { ChannelMembers } from './src/entities/ChannelMembers';
import { Channels } from './src/entities/Channels';
import { DMs } from './src/entities/DMs';
import { Users } from './src/entities/Users';
import { WorkspaceMembers } from './src/entities/WorkspaceMembers';
import { Workspaces } from './src/entities/Workspaces';

dotenv.config();

const dataSource = new DataSource({
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: [join(__dirname, '/src/entities/*.ts'),
  charset: 'utf8mb4',
  synchronize: false,
  logging: true,
  migrations: [
    process.env.NODE_ENV === 'production'
      ? join(__dirname, '../dist/src/migrations/*{.ts,.js}')
      : join(__dirname, '/src/migrations/*{.ts,.js}'),
  ],
});

export default dataSource;

환경변수 NODE_ENV 에 따라 마이그레이션 파일이 저장되는 디렉토리를 다르기 설정했습니다.

개발 환경인 경우 src/migrations 하위에

실제 배포 환경인 경우 dist/src/migrations 하위에

package.json 에서 script 작성하기

package.json

{
  "scripts": {
    "db:migrate": "npx typeorm-ts-node-esm migration:run -d ./dataSource.ts",
    "db:migrate:revert": "npx typeorm-ts-node-esm migration:revert -- -d ./dataSource.ts",
    "db:create-migration": "npx typeorm migration:create -o ./src/migrations/",
    "db:generate-migration": "npx typeorm migration:generate -o ./src/migrations -d ./dataSource.ts"
  }
}

npm run db:create-migration : 마이그레이션 파일 생성(boilerplate)

npm run db:generate-migration : entity의 모델과 DB를 비교하여 변경 사항에 대한 마이그레이션 파일 생성

npm run db:migrate : 마이그레이션 파일의 up 메서드 바디 실행 후 DB 에 적용

npm run db:migrate:revert : 마이그레이션 파일의 down 메서드 바디 실행 후 DB 에 적용(이전 상태로 rollback)

참고로, npm run db:generate-migration 명령을 실행하면 컬럼의 길이, 타입 변경 시 해당 테이블의 컬럼을 DROP 한 뒤, 재 생성하는 방식으로 이뤄지기 때문에 기존 컬럼에 있던 데이터가 전부 삭제될 수 있습니다.
그러므로 되도록 사용하는 걸 권장하지 않는다고 합니다. 특히, 실제 동작하고 있는 서비스에서 더더욱요!
migration:run 과 migration:revert 명령은 js 파일에 대해서만 실행 가능합니다. 그러나 typeorm migration:generate 와 typeorm migration:create 명령을 통해 생성된 마이그레이션 파일은 ts 파일입니다.
migration:run 과 migration:revert 명령을 실행하기 전 .ts 형식의 파일을 .js 형식의 파일로 트랜스파일해줘야 합니다.
공식 문서 권장) .ts 형식의 마이그레이션 파일을 실행하기 위해 typeorm 과 관련하여 ts-node 를 사용합니다.

출처 : https://typeorm.io/migrations#running-and-reverting-migrations 

npm run db:create-migration 명령을 통해 마이그레이션 파일 생성 후 query 작성하기

src/migrations/1669545919843-migrations.js

const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class migrations1669545919843 {

    async up(queryRunner) {
      // 변경사항 query
    }

    async down(queryRunner) {
      // 변경사항을 되돌릴 query
    }

}

생성된 파일의 메소드에서 up에는 변경사항 query를 작성하고 down에는 변경사항을 되돌릴 query를 작성한다.

 

마이그레이션 실행시키기

마이그레이션 실행은 npm run db:migrate 와 npm run db:migrate:revert 명령어를 통해 진행되며 migrate는 up을 실행 시키는 것이고, migrate:revert 는 down을 실행시키는 것입니다.

기본적으로 마이그레이션을 진행할 때 TRANSACTION안에서 진행되며 만약 진행을 하다 오류가 발생하면 ROLLBACK이 진행됩니다.

 

처음 마이그레이션을 하는 경우 데이터베이스에 migrations라는 테이블이 생기며 이력들이 저장됩니다.

생성된 테이블 구조는 다음과 같습니다.

id timestamp name
     

출처

https://typeorm.io/migrations 

https://darrengwon.tistory.com/m/1311

https://velog.io/@dev_leewoooo/TypeORM%EC%9D%98-built-in-migration-%EC%9D%B4%EC%9A%A9%ED%95%98%EA%B8%B0

'라이브러리 & 프레임워크 > NestJS' 카테고리의 다른 글

Exception filter  (0) 2022.11.28
Validation  (0) 2022.11.28
Mapped Types  (0) 2022.11.28
[TypeORM] TypeORM 엔티티 상속을 통한 중복 코드 제거  (0) 2022.11.26
[NestJS] 커스텀 데코레이터 만들기  (0) 2022.11.21
복사했습니다!