flyway - Java 용 Database migration framework

소개

개발이나 운영을 하면서 변경 내역을 반영할 때 가장 많이 실수하는 부분은 Local 이나 특정 환경에서만 되게 개발/패키징하여 실제 운영환경에서는 의도한대로 동작하지 않는 것이었다.

Build/packaging/deploy 시 운영에서 벌어지는 실수들은 형상관리 및 CI 서버, 테스트 자동화, 배포 자동화등의 개발 프로세스를 적용하여 거의 다 해결이 되었지만

여전히 자주 발생하는 실수는 개발이나 테스트 시스템의 DB  스키마나 데이타만 바꾸고 실제 운영에서는 해당 스키마나 데이타 적용 절차를 빼먹는 것이다.

 

위와 같은 상황이 일어나는 이유는 Source 코드 및 db schema 가 정리된 ERD 나 SQL 파일은 형상 관리가 가능하지만 이 파일이 DBMS 에 반영됐는지 자체는 따로 관리가 되지 않기 때문이다.

물론 ERD 같은 솔루션에서 Reverse Engineering 을 해서 테스트 DB 와 운영 DB 의 스키마를 비교한다거나 하는 노가다를 하면 가능하지만 운영 DB 에 대해서 저런 작업을 하는건 민감하고 부담이 되는 작업이다.

                   

http://flywaydb.org/getstarted/whyDatabaseMigrations.html 에서 발췌

 

ruby on rails 나 laravel framework을 보고 가장 놀랐던것 중에 하나는 db:migration 기능이었다.

schema 파일을 sqlplus 나 mysql client 같은 곳에서 실행해서 반영하지 않고 DBMS schema version을 관리하는 테이블을 만들고 db:migrate version 명령어를 실행하면 버전에 맞는 스키마 및 SQL 문을 적용하는 방식으로 DBMS 를 관리하는 db:migration 기능은 서비스 개발/유지보수할때 꼭 필요한 기능이라는 생각이 들었다.

 

flyway 는 Java 에서 Ruby on Rails 나 Laravel 처럼 DB migration 을 할수 있는 프레임웍이다.

 

동작 방식

http://flywaydb.org/getstarted/howFlywayWorks.html

사용하는 DBMS 에 SCHEMA_VERSION 라는 테이블을 만들고 여기에 DBMS 의 버전 및 변경 스크립트, 변경일, 변경결과 등을 기록한다.


migration sql file 

 http://flywaydb.org/documentation/migration/sql.html

file name

  • 파일명은 대문자 V와 숫자로 시작해야 flyway 가 schema 파일로 인식함
  • V 뒤에 숫자(Dot 도 가능 - 1.1.3)로 버전명을 지정하고 underbar(_) 를 두 개 붙여야 함.
  • 이슈관리 시스템을 사용한다면 파일명에 Issue number 나 ticket 명을 붙이면 history 관리시 더 편리할 것 같음(Ex: V3__PRJ-123_Create_person_table.sql)

Sql Script syntax

  • Single- or multi-line statements
  • Flexible placeholder replacement
  • Single- (--) or Multi-line (/**/) comments spanning complete lines
  • Database-specific SQL syntax extensions (PL/SQL, T-SQL, ...)
Sample
/* Single line comment */
CREATE TABLE test_user (
  name VARCHAR(25) NOT NULL,
  PRIMARY KEY(name)
);

/*
Multi-line
comment
*/

-- Placeholder
INSERT INTO ${tableName} (name) VALUES ('Mr. T');

 

maven 적용 예제

  • JDK 5+ 및 maven 2, 3 필요

Create the project

  1. maven archetype 으로 예제 프로젝트 생성

    mvn archetype:generate -B -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart \
    -DarchetypeVersion=1.1 -DgroupId=foo -DartifactId=bar -Dversion=1.0-SNAPSHOT -Dpackage=foobar
  2. cd bar

 

pom 에 의존성 추가

<project ...>
    ...
    <dependencies>
        <dependency>
            <groupId>com.googlecode.flyway</groupId>
            <artifactId>flyway-core</artifactId>
            <version>2.3</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.3.170</version>
        </dependency>
        ...
    </dependencies>
    ...
</project>

Flyway와 통합

src/main/java/foobar/App.java 에 Flyway migration  및 사용할 DBMS 정보 설정

package foobar;

import com.googlecode.flyway.core.Flyway;

public class App {
    public static void main(String[] args) {
        // Create the Flyway instance
        Flyway flyway = new Flyway();

        // Point it to the database
        flyway.setDataSource("jdbc:h2:file:target/foobar", "sa", null);

        // Start the migration
        flyway.migrate();
    }
}

 

첫번째 migration 생성

  1. migration directory 생성

    mkdir -p src/main/resources/db/migration
  2. src/main/resources/db/migration/V1__Create_person_table.sql migration schema 생성

    create table PERSON (
        ID int not null,
        NAME varchar(100) not null
    );

첫번째 migration 실행

mvn 명령어로 첫번째 migration 실행

bar> mvn package exec:java -Dexec.mainClass=foobar.App
2014. 1. 15 오전 2:12:22 com.googlecode.flyway.core.metadatatable.MetaDataTableImpl createIfNotExists
정보: Creating Metadata table: "PUBLIC"."schema_version"
2014. 1. 15 오전 2:12:22 com.googlecode.flyway.core.command.DbMigrate migrate
정보: Current version of schema "PUBLIC": << Empty Schema >>
2014. 1. 15 오전 2:12:22 com.googlecode.flyway.core.command.DbMigrate applyMigration
정보: Migrating schema "PUBLIC" to version 1
2014. 1. 15 오전 2:12:22 com.googlecode.flyway.core.command.DbMigrate logSummary
정보: Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.052s).

두번째 migration 실행

src/main/resources/db/migration/V2__Add_people.sql 에 두번째 migration schema 작성

insert into PERSON (ID, NAME) values (1, 'Axel');
insert into PERSON (ID, NAME) values (2, 'Mr. Foo');
insert into PERSON (ID, NAME) values (3, 'Ms. Bar');
bar> mvn package exec:java -Dexec.mainClass=foobar.App
실행 결과
2014. 1. 15 오전 2:14:21 com.googlecode.flyway.core.command.DbMigrate migrate
정보: Current version of schema "PUBLIC": 1
2014. 1. 15 오전 2:14:21 com.googlecode.flyway.core.command.DbMigrate applyMigration
정보: Migrating schema "PUBLIC" to version 2
2014. 1. 15 오전 2:14:21 com.googlecode.flyway.core.command.DbMigrate logSummary
정보: Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.035s).

 

Command-line tools 로 migration

http://flywaydb.org/getstarted/firststeps/commandline.html

Maven plugin, API, Ant 등이 있지만 Command-line 이 제일 편리한것 같아서 cmd-tool로 db migration 정리

설치

  1. http://flywaydb.org/getstarted/firststeps/commandline.html 에서 다운로드
  2. unzip flyway-commandline-2.3.zip
  3. H2 driver 를 사용한다면 http://repo1.maven.org/maven2/com/h2database/h2/1.3.170/h2-1.3.170.jar 에서 다운로드후에 flyway의 jars 폴더에 복사
  4. MySQL 은 http://dev.mysql.com/downloads/connector/j/ 에서 driver 다운로드후 jars 에 복사
  5. 압축 풀린 폴더를 /usr/local 등에 이동하고 profile 에 패스 설정하거나 alias 로 연결

    alias flyway='/usr/local/flyway-2.3/flyway'
    export PATH=$PATH:/usr/local/flyway-2.3/

설정

conf/flyway.properties 에 필요한 설정 추가

# Jdbc url to use to connect to the database
flyway.url=jdbc:h2:file:./foobardb
## MySQL
#flyway.url=flyway.url=jdbc:mysql://localhost:3306/lesstif?useUnicode=true&characterEncoding=utf8

# User to use to connect to the database (default: <<null>>)
flyway.user=SA
 
## schema sql 을 저장할 폴더. 기본적으로 flyway 설치폴더밑에 sql 에서 찾게 된다. 여러개를 지정할 경우 , 로 구분한다.
## filesystem 또는 classpath 로 지정한다. ${HOME} 같이 property 기반 설정은 동작하지 않는다.
flyway.locations=filesystem:/home/lesstif/work/bar/sql

사용법

flyway [options] command

command

CommandsDescription 
clean스키마 정보 테이블의 migration 된 모든 오브젝트를 지운다. 운영 환경이라면 심각한 문제를 일으킬 수 있으니 주의. 
initmetadata table 을 생성 및 초기화한다. 이미 생성되어 있다면 에러를 발생시킨다, 
migrateMigrates the database 
validateValidates the applied migrations against the ones on the classpath 
infoPrints the information about applied, current and pending migrations 
repairRepairs the metadata table after a failed migration 

주요 options

config 에 있는 설정 항목들을 runtime 에 options 으로 처리 가능

-X : 디버깅용 자세한 출력

 

첫번째 migration 생성

위 sql 디렉토리 밑에 V1__Create_person_table.sql 생성

V1__Create_person_table.sql
create table PERSON (
    ID int not null,
    NAME varchar(100) not null
);

첫번째 migration 실행

flyway migrate
 /usr/bin/tput
Flyway (Command-line Tool) v.2.3
Creating Metadata table: "PUBLIC"."schema_version"
Current version of schema "PUBLIC": << Empty Schema >>
Migrating schema "PUBLIC" to version 1
Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.087s).

두번째 migration 실행

sql 디렉토리 밑에 V2__Add_people.sql 생성

V2__Add_people.sql
insert into PERSON (ID, NAME) values (1, 'Axel');
insert into PERSON (ID, NAME) values (2, 'Mr. Foo');
insert into PERSON (ID, NAME) values (3, 'Ms. Bar');
flyway migrate
/usr/bin/tput
Flyway (Command-line Tool) v.2.3

Current version of schema "PUBLIC": 1
Migrating schema "PUBLIC" to version 2
Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.056s).

migration 정보 확인

flyway info
+----------------+-----------------------------------------------------------------------------------------+---------------------+---------+
| Version        | Description                                                                             | Installed on        | State   |
+----------------+-----------------------------------------------------------------------------------------+---------------------+---------+
| 1              | Create person table                                                                     | 2014-01-15 17:34:52 | Success |
| 2              | Add people                                                                              | 2014-01-15 17:34:52 | Success |
+----------------+-----------------------------------------------------------------------------------------+---------------------+---------+

 

command line arguent overide

driver나 sql 폴더등이 config 에 지정되어 있으므로 여러 개의 DB 가 있거나 여러 프로젝트를 구동할 경우 flyway의 config 옵션을 수정해야 하는 불편함이 있다. 이를 위해 구동시 설정값을 바꿀수 있는 기능을 제공한다.

형식은 -key=value 이며 key 값은 config 에 지정된 값에서 flyway 라는 prefix 를 제거하면 된다.

migration 용 SQL  파일은 locations property 이고 JDBC driver 등을 찾는 property는 jarDir 이다.

flyway -url="jdbc:mysql://localhost:3306/lesstif?useUnicode=true&characterEncoding=utf8" -user=dbusers -password=dbpwd -locations=/home/lesstif/proj1/sql -jarDir=/home/lesstif/prj1/flyway-jars

 

소감

  • flyway 는 open source 이나 상용 기술 지원으로 돈을 버는 사업 모델 채택함. 그래서 일반적인 오픈소스보다 문서 품질이 매우 훌륭함
  • maven, command-tool, API, Gradle, Ant 등과 연계해서 사용 가능하나 개인적으로는 command-tool 이 실제 운영 환경에 migrate 하기에 적합한것 같음
    • 운영 환경에서 web app 디플로이를 maven 으로 바로 하지는 않으므로 db migration 작업에 메이븐을 사용하는 것 보다는 커맨드 라인 방식이 적절
  • ruby on rails 와 달리 rollback 이 없으나 실제로 db 작업시 rollback 은 리스크가 크므로 적용하지 않는다고 하니 빠져도 상관은 없을듯
  • schema 의 버전은 파일명으로 판단하므로 개발자가 버전을 직접 지정해 주어야 함. 파일의 버전이 같으면 에러가 발생하므로 실수 여지는 적을 것 같음
  • flyway clean 명령어는 적용된 migration 을 모두 삭제하므로 매우 위험하나 별도의 안전장치가 없음
    • 최초에 스키마 생성을 flyway 로 하고 운영중에 clean 명령을 실행하면 모든 table 이 drop 되어 버리므로 심각한 상황이 발생함
  • 운영환경에서 clean 명령은 사용하지 못하게 하는 안전 장치가 필요함
    • 소스를 수정해서 운영용(clean 사용 불가)과 개발/테스트용 flyway(clean 사용 가능) 로 나누어 사용하는건 매우 번거롭고 flyway 새 버전이 나오면 소스 수정을 해야 하므로 좋은 방법은 아닌것 같음

 

Ref

http://flywaydb.org/