Flyway 공부

얼마전 프로젝트에서 Flyway를 적용해볼까 하는 마음에 공부를 했던 내용을 간단히 기록해둡니다. JPA를 사용하면서 어떻게 하면 DB 변경 사항(특히, 스키마)을 관리할 수 있을까 하는 마음에 공부를 시작하게 됐네요.. (하지만 정작 프로젝트를 못하는 바람에 일단은 공부로 끝..)

소개

Flyway는 DB 마이그레이션을 도와주는 오픈소스 프로그램입니다. 구체적으로는 다음 내용을 도와줍니다.

특히나 DB 변경 작업의 이력을 관리하고 문제가 발생하면 이를 다시 고치고 반영하는 절차가 있다는 점이 개인적으로는 마음에 드네요. 마이그레이션하려면 반드시 파일(또는 Java 클래스)로 만들어야 하기 때문에 이력 뿐만 아니라 마이그레이션 했던 코드(SQL)가 소스관리까지 되는 것도 장점입니다.

사용방법

시작하기 문서를 보니 flyway는 여러 가지 방식으로 사용이 가능하네요. 콘솔에서 직접 실행할 수도 있고, 자바 API를 사용하거나 자바를 지원하는 여러 툴(maven, gradle 등)과 함께 사용할 수도 있습니다.

마지막으로는 자동화를 고려해 봐야겠지만 저는 소규모 팀에서 DB를 책임지는 사람이 직저 검토하고 반영하는 절차를 원했기 때문에 커맨드라인 방식으로 테스트를 해봤습니다.

설치

다운로드 페이지에서 받아서 사용하면 되는데 맥을 사용하면 brew로 쉽게 설치가 가능합니다

$ brew install flyway

다른 라이브러리나 프로그램 필요 없이 독립적으로 실행됩니다. Good.

실행

커맨드라인에서는 flyway 명령어로 쉽게 실행이 가능합니다.

$ flyway [options] command

command(이하 한글로 커맨드로 적습니다)는 flyway 실행 방식으로 총 6 가지를 제공합니다. (migrate, clean, info, validate, baseline)

제공하는 옵션은 매우 다양합니다. flyway라고 처보면 아래처럼 설명이 잘 나와요.. (사이트 문서화도 잘되어 있는 편이지만 그보다 프로그램 실행 중 만나는 로그나 가이드 등이 꽤 잘되어 있습니다)

$ flyway
Flyway 4.0 by Boxfuse

Usage
=====

flyway [options] command

By default, the configuration will be read from conf/flyway.conf.
Options passed from the command-line override the configuration.

Commands
--------
migrate  : Migrates the database
clean    : Drops all objects in the configured schemas
info     : Prints the information about applied, current and pending migrations
validate : Validates the applied migrations against the ones on the classpath
baseline : Baselines an existing database at the baselineVersion
repair   : Repairs the metadata table

Options (Format: -key=value)
-------
driver                       : Fully qualified classname of the jdbc driver
url                          : Jdbc url to use to connect to the database
user                         : User to use to connect to the database
password                     : Password to use to connect to the database
schemas                      : Comma-separated list of the schemas managed by Flyway
table                        : Name of Flyway's metadata table
locations                    : Classpath locations to scan recursively for migrations
resolvers                    : Comma-separated list of custom MigrationResolvers
skipDefaultResolvers         : Skips default resolvers (jdbc, sql and Spring-jdbc)
sqlMigrationPrefix           : File name prefix for sql migrations
repeatableSqlMigrationPrefix : File name prefix for repeatable sql migrations
sqlMigrationSeparator        : File name separator for sql migrations
sqlMigrationSuffix           : File name suffix for sql migrations
encoding                     : Encoding of sql migrations
placeholderReplacement       : Whether placeholders should be replaced
placeholders                 : Placeholders to replace in sql migrations
placeholderPrefix            : Prefix of every placeholder
placeholderSuffix            : Suffix of every placeholder
target                       : Target version up to which Flyway should use migrations
outOfOrder                   : Allows migrations to be run "out of order"
callbacks                    : Comma-separated list of FlywayCallback classes
skipDefaultCallbacks         : Skips default callbacks (sql)
validateOnMigrate            : Validate when running migrate
ignoreFutureMigrations       : Allow future migrations when validating
cleanOnValidationError       : Automatically clean on a validation error
cleanDisabled                : Whether to disable clean
baselineVersion              : Version to tag schema with when executing baseline
baselineDescription          : Description to tag schema with when executing baseline
baselineOnMigrate            : Baseline on migrate against uninitialized non-empty schema
configFile                   : Config file to use (default: conf/flyway.properties)
configFileEncoding           : Encoding of the config file (default: UTF-8)
jarDirs                      : Dirs for Jdbc drivers & Java migrations (default: jars)

설정 파일

물론 위 옵션을 다 사용하지는 않지마 실행할때마다 매번 입력하기는 여간 힘든 일이 아니니 당연히 설정 파일로 만들어 두고 사용하는 편이 좋겠죠.

설정 파일은 ‘키=밸류’ 형식의 프로퍼티 파일로 만들고 실행 시에 -configFile로 경로를 넘겨주면 됩니다. 경로는 상대경로와 절대경로(filesystem:)를 모두 사용할수 있습니다.

$ flyway -configFile=db-migration/flyway.conf

설정 파일과 아래 이어서 나오는 마이그레이션 파일은 소스폴더에 적절히 위치를 잡아서 함께 두는 편이 좋겠네요.

설정 파일 샘플은 아래처럼 만들어 테스트해봤습니다.

-- 운영이라 생각하고 계정을 분리
flyway.url=jdbc:mysql://localhost:3306/commerce_prod
flyway.user=commerce
flyway.password=1234
flyway.driver=com.mysql.jdbc.Driver
flyway.locations=filesystem:db-migration/sql
flyway.table=DATABASE_MIGRATION

마이그레이션 파일

마이그레이션 파일은 이 문서을 참고해 작성하면 됩니다.

특히나, 파일 이름을 잘 만들어 줘야 합니다(위 링크에서 Naming 절 참조). 버전과 설명(Description)을 잘 달아줘야 합니다. 파일 이름에서 설명 부분(Description)이 이력 테이블에 그대로 기록되기 때문에 나중에 찾아 볼때도 쉽게 찾아갈 수 있도록 적어주는 것이 주요해 보이네요. (역시나 어디서나 작명이 중요..)

마이그레이션 파일 네이밍(Flyway 사이트 발췌)

파일명을 잘못 기록해주면 아래처럼 에러가 발생합니다.

ERROR: Wrong migration name format: V1_FIRST_CREATE_SCHEMA.sql(It should look like this: V1_2__Description.sql)

파일 이름 만으로 파일을 구분하기 때문에 당연히 이름이 변경되면 새로운 마이그레이션 파일로 처리됩니다. (이력 테이블에도 새로 추가) 파일 위치는 기본 규칙을 따라도 되지만 설정 파일에서 직접 정해준 경로를 사용할 수도 있습니다.

마이그레이션 파일은 기본적으로 SQL 문을 포함하는 파일(.sql)로 만들면 되지만, 자바 클래스를 사용할수도 있습니다. 두 가지 방법을 동시에 활용할 수도 있습니다. 무언가 동적인 마이그레이션이거나 스키마보다는 데이터의 마이그레이션이 필요한 경우 활용할 수 있겠네요.

커맨드

migrate

마이그레이션을 실행하는 커맨드입니다.

$ flyway -configFile=db-migration/flyway.conf migrate

DB에 반영되지 않은 마이그레이션 파일을 순서대로 실행합니다. 아직 반영되지 않은 마이그레이션 파일을 낮은 버전부터 순서대로 실행합니다. 처음으로 추가한 파일이라면 실행하면서 이력 테이블에 새롭게 추가 해줍니다.

실행 결과는 아래처럼 콘솔에 출력이 됩니다. (3개 마이그레이션 파일을 실행한 결과)

$ flyway -configFile=db-migration/flyway.conf migrate
Flyway 4.0 by Boxfuse

Database: jdbc:mysql://localhost:3306/commerce_prod (MySQL 10.0)
Successfully validated 3 migrations (execution time 00:00.006s)
Creating Metadata table: `commerce_prod`.`DATABASE_MIGRATION`
Current version of schema `commerce_prod`: << Empty Schema >>
Migrating schema `commerce_prod` to version 1 - INITIALIZE DB SCHEMA
Migrating schema `commerce_prod` to version 2 - CREATE TEST TABLE
WARNING: DB: Unknown table 'commerce_prod.test_table' (SQL State: 42S02 - Error Code: 1051)
Migrating schema `commerce_prod` to version 3 - CHANGE ADDRESS FIELD
Successfully applied 3 migrations to schema `commerce_prod` (execution time 00:00.505s).

info

info 커맨드는 이력 테이블을 콘솔에 출력해줍니다. 나름 이쁘게 잘 나옵니다^^.

$ flyway -configFile=db-migration/flyway.conf info
Flyway 4.0 by Boxfuse

Database: jdbc:mysql://localhost:3306/commerce_prod (MySQL 10.0)

+---------+----------------------+---------------------+---------+
| Version | Description          | Installed on        | State   |
+---------+----------------------+---------------------+---------+
| 1       | INITIALIZE DB SCHEMA | 2016-08-23 02:08:27 | Success |
| 2       | CREATE TEST TABLE    | 2016-08-23 02:08:27 | Success |
| 3       | CHANGE ADDRESS FIELD | 2016-08-23 02:08:27 | Success |
+---------+----------------------+---------------------+---------+

info는 전체 필드를 보여주지는 않습니다. 생성되는 이력 테이블 전체 필드는 아래를 참조하세요.

CREATE TABLE `DATABASE_MIGRATION`
(
   installed_rank   int(11),
   version          varchar(50),
   description      varchar(200),
   type             varchar(20),
   script           varchar(1000),
   checksum         int(11),
   installed_by     varchar(100),
   installed_on     timestamp DEFAULT 'CURRENT_TIMESTAMP',
   execution_time   int(11),
   success          tinyint(1)
)

clean

clean은 조심해서 실행해야 합니다.

clean은 이력 테이블만 초기화 해주는 것이 아니라 해당 DB 자체를 초기화(Drops all objects)하기 때문에 테이블 전체가 삭제됩니다. 실제로 운영에서는 사용지 않도록 조심해야 하겠습니다.. 가이드에서도 운영(production)에서는 절대 사용하지 말라고 경고하고 있습니다.

repair

마이그레이션 도중에 어떠한 이유로든 실패하게 되면 그 시점에서 마이그레이션 진행이 중지됩니다.

ERROR: Migration of schema `commerce_prod` to version 2 - CREATE TEST TABLE failed! Please restore backups and roll back database and code!
ERROR:
Migration V2__CREATE_TEST_TABLE.sql failed
------------------------------------------
SQL State  : 42S22
Error Code : 1054
Message    : Unknown column 'f2' in 'field list'
Location   : db-migration/sql/V2__CREATE_TEST_TABLE.sql (/Users/chanwook/src/commerce-model/db-migration/sql/V2__CREATE_TEST_TABLE.sql)
Line       : 3
Statement  : INSERT INTO TEST_TABLE(f2) VALUES('블라블라')

이때 info로 이력 테이블을 확인해보면 해당 마이그레이션 상태가 FAILED로 나오고, 다시 migarate를 해도 체크섬 에러가 발생하게 됩니다.

Database: jdbc:mysql://localhost:3306/commerce_prod (MySQL 10.0)
ERROR: Validate failed: Migration checksum mismatch for migration 2
-> Applied to database : -309408409
-> Resolved locally    : 394998005

만약, 5개 중 3번째에서 실패가 됐다면 앞선 2개 파일은 정상 반영되고(SUCCESS), 실패한 3번째 파일은 실패 상태(FAILED)가 되며, 실행 조차 못한 2개 파일은 대기(PENDING) 상태가 되버리게 됩니다.

이 때 repair를 실행하면 실패한 3번째 파일의 상태가 대기(PENDING) 상태로 변견되며, 그제서야 다시 마이그레이션을 실행할 수 있는 상태가 됩니다. 이제 마이그레이션 파일의 문제를 해결하고 다시 migarate를 하면 ok.

Validate

validate 커맨드는 DB에 반영된 이후 마이그레이션 파일이 변경됐는지 확인해주는 기능입니다.

마이그레이션 완료된 파일을 변경하고 validate를 실행해보면 체크섬 에러가 발생합니다.

$ flyway -configFile=db-migration/flyway.conf validate
Flyway 4.0 by Boxfuse

Database: jdbc:mysql://localhost:3306/commerce_prod (MySQL 10.0)
ERROR: Validate failed: Migration checksum mismatch for migration 3
-> Applied to database : 812327141
-> Resolved locally    : 769275282

마이그레션이을 하고 나서는 아무래도 변경 상태를 확인하기가 어려운데 다음 번 마이그레이션 하기 전에 validate로 반드시 확인을 하고 실행하는 편이 좋을듯 합니다.

주의할 점은 마이그레이션 파일의 변경 여부를 확인해주는 것이지 DB와 상호 확인하는 건 아니라는 점입니다.

마치며

변경 사항(스키마 변경이나 각종 기준 정보 변경)을 관리할 수 있게 해주는 점이 장점이자 제대로 flyway를 사용하기 위해 꼭 필요한 조건으로 보입니다.

개발 프로젝트에서도 개발 기간 동안에 변경되는 DB를 추적하고 공유하는 것이 쉬운 일이 아닌데 이러한 용도로도 사용하는 방법도 괜찮아 보입니다. 개발 기간에는 마이그레이션 파일을 계속 추가하는 것보다 기존 마이그레이션 파일을 직접 수정해서 clean -> migrate로 사용하고, 프로젝트 주요 마일스톤이나 운영 반영을 기점으로 베이스라인을 잡아주는 방법도 있겠네요..

공부는 이정도에서 마무리하고 다음 번에는 현장에 적용하고 경험담을 기록하는 것으로 하겠습니다.