TypeORM 마이그레이션을 수행하다가 프로젝트의 migration history와 db에 존재하는 migration history가 동일한데 마이그레이션을 반영하는 경우 이전 마이그레이션 파일이 계속 감지되는 경우가 발생했다.
한번 재연해보자.
환경은 Nestjs 11버전, postgresql 14버전입니다.
프로젝트의 history와 db의 history가 아무것도 없을 때 새로운 Entity를 하나 생성해보자.
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('user')
export class User {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ type: 'char', length: 40 })
uid: string;
@Column({ type: 'varchar' })
name: string;
@Column({ type: 'varchar' })
email: string;
}
마이그레이션도 생성해보자.
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Init1745927893488 implements MigrationInterface {
name = 'Init1745927893488';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "uid" character(40) NOT NULL, "name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
DB에도 반영했다.
typeorm=# SELECT * FROM migrations;
id | timestamp | name
----+---------------+-------------------
1 | 1745927893488 | Init1745927893488
이런 상황에서 마이그레이션을 반영하려고 하면 반영할 마이그레이션이 없기 때문에 아래와 같이 나온다.
query: SELECT version()
query: SELECT * FROM current_schema()
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = 'public' AND "table_name" = 'migrations'
query: SELECT * FROM "migrations" "migrations" ORDER BY "id" DESC
No migrations are pending
그럼 어떤 경우에 문제상황이 만들어질까?
프로젝트에 파일 이름을 변경해보자.
기존
# 기존
migrations
└── 1745927893488-init.ts
# 변경후
migrations
└── 1745927893488-change.ts
결과는 같게 나온다.
그럼 마이그레이션 파일 class name을 변경해보자.
# 기존
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Init1745927893488 implements MigrationInterface {
name = 'Init1745927893488';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "uid" character(40) NOT NULL, "name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
# 변경후
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Change18273921 implements MigrationInterface {
name = 'Init1745927893488';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "uid" character(40) NOT NULL, "name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
그래도 결과는 같게 나온다.
그럼 DB에 있는 마이그레이션 이름을 변경해보자.
# 기존
id | timestamp | name
----+---------------+-------------------
1 | 1745927893488 | Init1745927893488
# 변경후
id | timestamp | name
----+---------------+---------------
1 | 1745927893488 | Change1283917
(1 row)
1 migrations are already loaded in the database.
1 migrations were found in the source code.
Change1283917 is the last executed migration. It was executed on Tue Apr 29 2025 20:58:13 GMT+0900 (대한민국 표준시).
1 migrations are new migrations must be executed.
그럼 프로젝트에 있는 마이그레이션 파일의 name 필드를 변경해보자.
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Init1745927893488 implements MigrationInterface {
name = 'Init1745927893466';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "uid" character(40) NOT NULL, "name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
TypeORM에서 마이그레이션 감지에 영향을 주는것은 프로젝트에 존재하는 마이그레이션 파일명이나 마이그레이션 클래스명이 아닌 클래스의 name
속성이나 DB migration table(default는 migrations)의 name
컬럼이란 것을 알 수 있다.
마이그레이션 클래스의 name
속성이 타임스탬프 값도 함께 이루어져있는데 조금 수정되어있어서 찾느라 오래걸렸다.
모두 소중한 시간을 아끼면 좋겠다.