본문 바로가기
공부/프로젝트

Flyway를 사용하는 이유

by JERO__ 2022. 7. 29.

Flyway

Flyway는 DB의 형상관리를 목적으로 하는 툴이다.

1. DB Migration 필요한 것일까?

현재 우리는 배포 후 데이터를 관리해본 경험이 없다.

유지 보수 중 스키마 구조가 바뀌는 상황에 어떻게 대처할 것인가?

예제로 알아보자

현재 유지보수 중인 테이블 (SampleEntity)이 다음과 같다고 가정하자!

@Entity
public class SampleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

헌데, 새로운 요구사항이 추가되었다. age 필드를 추가해야한다.

그렇다면, Entity에서는 단순히 private Integer age를 추가하면 된다.

@Entity
public class SampleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer age;
}

 

하지만 실제 DB의 상황에서 변화를 어떻게 처리할 것인가?

운영환경이라고 가정하자. 현재 운영환경의 테이블에는 데이터가 담겨있다.

 

 

방법1. DB 서버에 들어가 테이블 직접수정한다

해당 DB에 접속하여 다음의 스키마를 입력해준다. 하지만 단점이있다.

ALTER TABLE sample_entity ADD COLUMN age integer default 0
  • 실수하기 좋다
  • 작업하기 번거롭다
  • 만약 데이터가 미리 있고, Entity쪽만 변경(age추가), DB 스키마를 수정하지 않았다면? 아래와 같은 에러가 발생할 것이다.
Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'age' in 'field list'

 

방법2. Flyway 툴을 사용하자

궁극적인 목표

  1. DB에 접속해서 TABLE을 직접 건들지 않으며, age 컬럼 포함한 신규데이터를 저장한다.
  2. 이전 나이가 포함되지 않는 데이터는 나이 필드를 0으로 초기화한다.
  3. git으로 관리한다 → 파일로 관리할 수 있어야 한다.

Flyway를 사용해보자

1. 의존성 추가 build.gradle

dependencies {
    implementation('org.flywaydb:flyway-core:6.4.2')
}

2. 어플리케이션 설정 추가 : application.yml

  • data source 설정 (DB정보)
spring: 
	datasource: 
    	driver-class-name: org.h2.Driver 
        url: jdbc:h2:tcp://localhost/~/flyway 
        username: sa 
        password:
  • flyway 설정

 

spring:
  jpa:
    generate-ddl: false
  flyway:
    enabled: true
  • Spring boot 2 이상의 경우 아래 설정 추가
spring: 
	flyway: 
    		baseline-on-migrate: true

 

3. init 파일 추가: resources/db/migration/

등록하려는 시점의 DB 스키마 구조를 입력해야 한다.

이 파일을 기준으로 flyway가 DB버전 관리를 하게된다.

  • 버전 관리가 어렵다면 일단 V {숫자}__{설명}. sql 으로 생각하자. (더 자세한 내용은 아래내용을 참조하자)

V1__init.sql :등록시점의 DB 스키마 구조 입력

drop table if exists sample_entity;

create table sample_entity(
      id   bigint auto_increment,
      name varchar(255),
      primary key (id)
);

 

엔티티 변경 적용

1. 컬럼 추가 (변경 전 ddl-auto: validate로 설정한다)

public class SampleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private int age;   // 추가한 부분
}

2. V2__add_age.sql

ALTER TABLE sample_entity ADD COLUMN age integer default 0

프로덕션 코드 실행

프로덕션 코드를 실행하면 flyway_schema_history 테이블이 생성된다.

기존테이블엔 V1만 적용되어 있었다. 다음 버전인 V2 이상이 들어오면 해당되는 것들을 실행하고 테이블을 갱신한다.

 

AGE가 추가되었다.

 

버전관리는 어떻게 하는가?

Prefix

  • V(Versioned): 현재버전을 새로운 버전으로 업데이트
  • U(Undo): 현재 버전을 이전버전으로 되돌리는 경우
  • R(Repeatable): 버전에 관계 없이 매번 실행하는 경우

Version

스크립트의 버전을 이전보다 꼭 높게 적어야 한다.

  • V와 U는 버전을 명시한다.
  • R은 버전을 명시하지 않는다. (매번 실행되기 때문이다)

Separator

무조건 __(언더바 2개)로 작성

Description

스크립트 내용에 맞게 자유롭게 적는다.

단어 구분은 _(언더바 1개)로 한다.

 

 

트러블슈팅

테스트 환경에서의 Flyway 실패

1. h2 테스트 환경

  1. h2에서 jdbc:h2:mem:testdb;MODE=MYSQL 을 적용
  2. V1__init.sql의 engine=InnoDB default charset utf8mb4; 부분에서 에러가 발생함

해결방법

  • h2 문법으로 적용하면 성공이 됨 → 스키마를 h2, MySQL 둘다 관리해야 하기 때문에 적용X
  • 로컬 test DB를 MySQL로 변경

2. MySQL 테스트 환경

  1. h2에서 MySQL로 마이그레이션 적용
  2. 테스트 격리가 안되는 현상 발생 (기존 테스트 실패)

해결방법

  • 프로덕션 코드는 build가 통과한다. → 엔티티와 DB스키마가 일치한다 → 테스트에 Flyway 스키마를 적용할 필요가 없다
    • Flyway + MySQL : 배포서버, 로컬서버 프로덕션 코드
    • h2 : 배포서버, 로컬서버 테스트 코드

3. 테스트 코드 yml 설정

  • flyway.enabled=false를 꼭 설정하자

4. 42001 에러

기존 테이블 내용과 새롭게 추가된 스키마 사이 추가되는 과정에서 중복된 내용이 있거나 관리되는 스키마 내용이 다를 때 에러가 발생한다.

  • drop table flyway_schema_history 를 적용, ddl auto를 validate 한 뒤 실행해보자.

댓글