59. NestJS Typeorm Migration (항해 74일차)

코이그·2023년 6월 15일

항해99

목록 보기
54/54

문제

현재 진행 중인 프로젝트에서 데이터베이스의 stores라는 테이블에 새로운 컬럼을 추가해야 하는 일이 생겼다.
pgadmin4에서 쿼리문으로 'ALTER TABLE stores ADD COLUMN coordinates geometry(Point, 4326) default NULL' 이런 식으로 컬럼을 생성했다.

그런 다음에 stores 테이블에 기존에 있던 Ma, La 값을 가지고 coordinates의 값을 업데이트 해주었다.

처음에는 잘 되다가 중간에 갑자기 서버가 끊기면서 해당 컬럼이 존재하지 않는다는 오류가 떴다. 그리고 실제로 pgadmin4에 봤더니 컬럼이 삭제되어있었다.

REFERENCES

시도

아직까지도 정확한 이유는 모르지만 어쨌든 migration으로 스키마의 변경 사항을 버전 관리도 하고 데이터베이스의 변경을 추적하고, 자동으로 적용할 수 있다는 이점 덕에 더 안전한 관리가 가능하다고 생각이 들어서 migration을 만들어보기로 했다.

위의 reference들을 따라하면서 진행했다.

migration 생성

npx typeorm migration:create ./src/migrations/addColumn

  • src/migrations 폴더에 {TIMESTAMP}-addColumn.ts 이라는 파일이 생김
// {TIMESTAMP}-addColumn.ts
            
import { MigrationInterface, QueryRunner } from 'typeorm';
            
export class AddColumn1686766534159 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {}
            
  public async down(queryRunner: QueryRunner): Promise<void> {}
}
  • up: 마이그레이션을 run 시 수행하고자 하는 query문 작성
  • down: 마이그레이션 revert 시 수행하고자 하는 query문 작성
  • 예시
// 1686766534159-addColumn.ts
                
import { MigrationInterface, QueryRunner } from 'typeorm';
                
export class AddColumn1686766534159 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(
      `ALTER TABLE "stores" ADD COLUMN "coordinates" geometry(Point, 4326) DEFAULT NULL`,
    );
  }
                
  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`ALTER TABLE "stores" DROP COLUMN "coordinates"`);
  }
}
  • 해당 entity에도 column을 추가해야 함.
// stores.entity.ts

import { Users } from '../auth/users.entity';
import { Reviews } from '../reviews/reviews.entity';
import { Tables } from '../tables/tables.entity';
import { Waitings } from '../waitings/waitings.entity';
import {
  BaseEntity,
  Column,
  CreateDateColumn,
  Entity,
  OneToMany,
  OneToOne,
  Point,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
            
@Entity()
export class Stores extends BaseEntity {
  @PrimaryGeneratedColumn()
  storeId: number;
            
  @Column()
  storeName: string;
            
  @Column()
  description: string;
            
  @Column()
  category: string;
            
  @Column()
  maxWaitingCnt: number;
            
  @Column()
  currentWaitingCnt: number;
            
  @Column('numeric')
  La: number;
            
  @Column('numeric')
  Ma: number;
  
  @Column()
  address: string;
  
  @Column({ nullable: true })
  distance: number;
  
  @Column({ nullable: true })
  oldAddress: string;
  
  @Column({ default: 60 })
  cycleTime: number;
  
  @Column()
  tableForTwo: number;
  
  @Column()
  tableForFour: number;
  
  @Column({ type: 'float', default: 0 })
  rating: number;
  
  // 얘다
  @Column('geometry', {
    spatialFeatureType: 'Point',
    srid: 4326,
    nullable: true,
  })
  coordinates: Point;
  
  @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;
  
  @UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  updatedAt: Date;
  
  @OneToMany(() => Waitings, (waiting) => waiting.store)
  waitings: Waitings[];
  
  @OneToMany(() => Reviews, (review) => review.store)
  reviews: Reviews[];
  
  @OneToOne(() => Tables, (table) => table.store)
  tables: Tables;
  
  @OneToOne(() => Users, (user) => user.store)
  user: Users;
}

migration 실행

  • 실행을 위해 DataSource 인스턴스가 있어야 한다.
// typeOrm.config.ts

import { Stores } from './src/stores/stores.entity';
import { ConfigService } from '@nestjs/config';
import { config } from 'dotenv';
import { DataSource } from 'typeorm';
import { AddColumn1686766534159 } from './src/migrations/1686766534159-addColumn';
import { Waitings } from './src/waitings/waitings.entity';
import { Reviews } from './src/reviews/reviews.entity';
import { Tables } from './src/tables/tables.entity';
import { Users } from './src/auth/users.entity';

config();
const configService = new ConfigService();
            
export const dataSource = new DataSource({
  type: 'postgres',
  host: configService.get('POSTGRES_HOST'),
  port: 5432,
  username: configService.get('POSTGRES_USERNAME'),
  password: configService.get('POSTGRES_PASSWORD'),
  database: 'temp',
  entities: [Stores, Waitings, Reviews, Tables, Users],
  migrations: [AddColumn1686766534159],
  // migrationsTableName: 'custom_migration_table',
});
            
dataSource
  .initialize()
  .then(() => {
    console.log('Data Source has been initialized!');
  })
  .catch((error) => {
    console.error('Error during Data Source initialization', error);
  });
  • import 할 때 경로의 앞에 꼭 “./”을 붙여줘야 한다. 그러지 않으면 module을 못 찾는다.

  • package.json의 scripts에는 아래 내용을 추가해야 한다.

"typeorm": "ts-node ./node_modules/typeorm/cli",
"typeorm:run-migrations": "npm run typeorm migration:run -- -d ./typeOrm.config.ts",
"typeorm:generate-migration": "npm run typeorm -- -d ./typeOrm.config.ts migration:generate ./migrations/$npm_config_name",
"typeorm:create-migration": "npm run typeorm -- migration:create ./migrations/$npm_config_name",
"typeorm:revert-migration": "npm run typeorm -- -d ./typeOrm.config.ts migration:revert"
  • migration 실행: npm run typeorm:run-migrations
    • 위 명령어를 실행하면 루트 디렉토리의 typeOrm.config.ts 파일을 찾아서 migrations 배열에 들어있는 migration들을 실행하게 된다.

새로운 migration을 생성하고 실행했을 때 적용이 안되는 이유?

  • migration revert는 가장 최근에 생성된? migration들만 down이 된다.
    1. typeOrm.config.ts의 migrations 배열에 AddColumn1686766534159만 있을 때 run을 하면 AddColumn1686766534159가 migrations db에 추가된다.
    2. typeOrm.config.ts의 migrations 배열에 AddColumnToTables1686791275164를 추가하고 run하면 AddColumn1686766534159는 이미 run되어있기 때문에 AddColumnToTables1686791275164만 run되고 migrations db에 추가된다.
    3. revert를 했을 때 가장 최근에 run된 AddColumnToTables1686791275164가 삭제된다.
    4. 또 한 번 revert를 해야 AddColumn1686766534159도 삭제된다.
  • migrations 배열에 처음부터 2개가 있다면 run을 했을 때 둘 다 migrations db에 추가되고 revert를 했을 때 둘 다 삭제된다.

해결

위 과정들을 거치고 나서 원하던 작업(stores 테이블에 새로 추가된 coordinates 값 업데이트)을 수행하니까 중간에 끊기지 않고 잘 수행이 된다.

원하는 51만여개의 row에 coordinates 컬럼의 값이 잘 업데이트 되었다.

알게 된 점

귀찮더라도... pgadmin4에서 쿼리로 컬럼 생성하는거보다 어렵더라도.. 잘 모르겠더라도.. 안전하고 더 체계적이고 유용한 마이그레이션을 통해 데이터베이스를 관리하는 것이 좋다..

profile
COYG🔴⚪

0개의 댓글