#database, #flyway

Flyway

깃-들다 프로젝트를 진행하며 DB 형상관리를 위해 Flyway를 도입했다. 여기서 DB 형상관리Flyway가 무엇일까? 이번 글에서 한번 알아보자.


Flyway?

Flyway 공식 문서에서는 Flyway를 다음과 같이 소개한다.

Flyway is an open-source database migration tool.

Flyway는 오픈소스 DB 마이그레이션 도구로, DB 형상관리를 위한 도구이다.

개발을 하며 한번 쯤은 형상관리 도구를 사용해본 경험이 있을 거다. 대표적으로 Git이 있다. 이는 소스코드 형상관리 도구이다. 결국 Flyway는 Git처럼 형상관리 도구인데, DB 형상관리를 할 때 쓰는 도구이다.


사용 이유

운영 환경에서는 spring.jpa.hibernate.ddl-auto 옵션을 create 또는 update로 할 수 없다. 그렇다면 DB 스키마에 변경이 생겼을 때, 직접 DB 서버에 접속해서 일일이 명령어를 쳐서 수정해야 한다. 하지만, 이렇게 하면 휴먼 에러가 발생할 가능성이 커진다. 오타를 낼 수도 있고, 실수로 명령어를 하나 빼먹을 수도 있다. 이때 Flyway와 같은 DB 형상관리 도구를 활용하면, 쉽고 안전하게 변경사항을 반영할 수 있다. 또, 스키마 버전 관리가 되기 때문에 어떤 변경사항이 있었는지 한눈에 볼 수 있다는 장점도 있다.


컨벤션

Flyway를 사용할 때, 스크립트의 위치와 네이밍을 조심해야 한다. 이들에 대한 컨벤션을 꼭 따라야 한다.


위치

기본 위치는 main/resources/db/migration이다. 즉 resources 패키지에 db/migration 패키지를 만들고, 이 경로에 스크립트 파일을 추가하면 된다.

물론 커스텀 설정도 할 수 있다. application.yml에서 spring.flyway.locations 옵션의 값을 classpath:db/migration/{다른_경로}와 같이 변경하면 된다.


네이밍

Flyway 스크립트의 네이밍은 다음과 같은 규칙을 가진다.

이미지 출처: Flyway Documentation - Naming


Prefix

V(Versioned), U(Undo), R(Repeatable)의 3가지 종류가 있다. V는 현재 버전을 새로운 버전으로 업데이트하는 경우, U는 현재 버전을 이전 버전으로 되돌리는 경우, R은 버전에 관계 없이 매번 실행하는 경우에 사용한다.

Version

V와 U는 버전을 명시해야 하고, R은 버전을 명시하지 않는다. R은 앞서 말했듯이 버전에 관련 없이 매번 실행되는 스크립트이기 때문이다.

새로 스크립트를 작성한다면, 스크립트의 버전을 이전보다 꼭 높게 적어야 한다. 예를 들어, 이전에 1.0였다면 이후에는 2.0과 같이 더 높은 숫자를 써야 한다. 이전보다 낮은 숫자를 쓰면, Flyway가 이를 무시하고 적용하지 않는다.

버전은 단순히 1, 2처럼 적을 수도 있고, 20211023처럼 날짜로 명시할 수도 있다.

Separator

무조건 __(언더바 2개)로 작성해야 한다.

Description

스크립트 내용에 맞게 자유롭게 적으면 된다. 단어 구분은 _(언더바 1개)로 한다.


적용 방법

설정 파일

build.gradle

먼저, Flyway 의존성을 추가한다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
    // 이 부분을 추가한다.
	implementation 'org.flywaydb:flyway-core:6.4.2'

	runtimeOnly 'com.h2database:h2'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

application.yml

그리고 Flyway와 관련한 옵션을 추가한다.

spring.flyway.enabled는 기본 설정값이 true지만, 직관적으로 명시하기 위해 적어줬다.
spring.flyway.baseline-on-migration변경 이력 테이블(버전 관리를 도와줌)을 생성하기 위해 작성했다. 변경 이력 테이블은 원래 자동으로 생성되나, 자동으로 생성되지 않는 경우도 있어 이를 대비하기 위해 넣어줬다. Spring Boot 2 이상을 사용하는 경우 추가하면 된다.

spring:
  # datasource는 각자에 맞게 작성한다.
  datasource:
    url: jdbc:h2:tcp://localhost/~/test;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
  jpa:
    show-sql: true
    properties:
      hibernate:
        dialects: org.hibernate.dialect.MySQL57Dialect
        format_sql: true
    generate-ddl: true
    hibernate:
      ddl-auto: validate
  # 이 부분을 추가한다.
  flyway:
    enabled: true
    baseline-on-migrate: true

V1 도입

소스코드

간단하게 Member 엔티티를 구성했다. 필드(컬럼)으로 id, name이 있고 id는 auto_increment 설정이 돼있다.

// Member.java
@Entity
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    String name;
}

스크립트

Flyway 스크립트는 다음과 같이 작성했다.

-- V1__init.sql
drop table if exists member;

create table member (
  id bigint auto_increment,
  name varchar(255),

  primary key (id)
);

결과

애플리케이션을 실행하고, h2에 접속하여 결과를 확인했다.

예상대로 Member 테이블이 생성됐다. 한편, flyway_schema_history 테이블도 생성됐다. 바로 이 테이블이 앞에서 말한 변경 이력 테이블이다. DB 스키마의 변경사항을 이곳에 기록한다.


해당 테이블의 내용을 보고 싶다면, 아래 명령어를 통해 확인할 수 있다.

select * from "flyway_schema_history";

이렇게 결과가 나타난다. 앞에서 작성한 V1 스크립트가 잘 기록됐다.

flyway_schema_history_v1


V2 도입

소스코드

이번에는 Member 엔티티에 company 필드(컬럼)을 추가했다.

// Member.java
@Entity
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String company;

스크립트

이어서 Flyway 스크립트를 다음과 같이 작성했다.

-- V2__add_company.sql
alter table member add column company varchar(255);

결과

애플리케이션을 다시 실행하고, 결과를 확인했다.

Member 테이블에 변경사항이 잘 반영됐고, 변경 이력 테이블에서 버전 관리가 되고 있다.
Member 테이블의 1행 데이터는 V1에서 삽입했고, 2행 데이터는 V2에서 삽입했다. 따라서 1행 데이터의 company 필드값은 null이고, 2행 데이터의 company 필드값은 null이 아닌 것을 확인할 수 있다.

flyway_schema_history_v2


주의할 점

Flyway는 DB 형상관리를 편리하게 해주는 장점이 있지만, 조심할 점이 있다.

DB 스키마에 변경사항이 생겼을 때(e.g. 엔티티에 필드가 추가/삭제되는 경우), 이를 Flyway에게 알려주는 스크립트를 잊지 말고 작성해야 한다. 이를 깜빡하면, 애플리케이션이 실행되지 않는 불상사가 발생한다. 그러므로 꼭꼭 스크립트를 작성하자. 그리고 이를 통해 DB 형상관리를 조금 더 우아하게 해보자.


References