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, ...)
/* 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
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
- 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 생성
migration directory 생성
mkdir -p src/main/resources/db/migration
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 정리
설치
- http://flywaydb.org/getstarted/firststeps/commandline.html 에서 다운로드
- unzip flyway-commandline-2.3.zip
- H2 driver 를 사용한다면 http://repo1.maven.org/maven2/com/h2database/h2/1.3.170/h2-1.3.170.jar 에서 다운로드후에 flyway의 jars 폴더에 복사
- MySQL 은 http://dev.mysql.com/downloads/connector/j/ 에서 driver 다운로드후 jars 에 복사
압축 풀린 폴더를 /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
Commands | Description | |
---|---|---|
clean | 스키마 정보 테이블의 migration 된 모든 오브젝트를 지운다. 운영 환경이라면 심각한 문제를 일으킬 수 있으니 주의. | |
init | metadata table 을 생성 및 초기화한다. 이미 생성되어 있다면 에러를 발생시킨다, | |
migrate | Migrates the database | |
validate | Validates the applied migrations against the ones on the classpath | |
info | Prints the information about applied, current and pending migrations | |
repair | Repairs the metadata table after a failed migration |
주요 options
config 에 있는 설정 항목들을 runtime 에 options 으로 처리 가능
-X : 디버깅용 자세한 출력
첫번째 migration 생성
위 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 생성
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