Engineering
DB도 형상관리를 해보자!
2018년 12월 11일
원문에서 보기 ↗개발 코드는 VCS(Version Control System)로 형상 관리를 하는데, DB 이력은 형상 관리를 할 수 있을까? 하고 고민하셨던 적이 있으신가요? 여기 DB 이력도 형상 관리를 할 수 있는 오픈 소스 도구 Flyway를 소개합니다.
버전별 DB 스크립트 파일은 아마 다음과 같이 스크립트 파일을 쭉~ 열거하여 관리하고 일일이 파일을 DBMS에서 실행하고 있었겠죠? 
물론, 스크립트 파일을 VCS로 형상 관리를 해도 되지만, 자칫 Human Fault가 발생할 수 있습니다. Application은 버전 상향이 되었는데, 변경된 DB 스크립트가 실행되지 않아, 장애로 이어질 수도 있으니깐요.
버전별 스키마 변경 사항 관리는? Application 구동을 위한 Provisioning data 관리는? 일일이 직접 기억하여 실행을 해줘야 할까요? 여기 Human Fault를 줄여줄 수 있는 Flyway 오픈 소스 DB 형상 관리 도구가 있습니다.
로컬, 알파 등 개발 DB에서 변경한 Schema, Index, Key 등을 베타, 운영 DB에 누락되는 것을 Flyway를 사용하여 방지할 수 있습니다. 또한 단위 테스트에서도 In-Memory DB(H2, derby, Hsqldb 등)에 DB DDL 이력을 실행하여 원격과 같은 DB 형상을 유지한 채, 단위 테스트를 할 수 있습니다.
가끔 단위 테스트를 알파 DB로 연동하여 진행하는 코드들이 있는데요. 단위 테스트는 환경적 요소에
영향을 받으면 안 됩니다. 즉, DB 서버가 연동되지 않아도 단위 테스트는 성공해야 참된 테스트 코드
라 할 수 있습니다. Flyway는 이를 가능케 하는 도구입니다.
Flyway 시작하기
먼저, 위 파일을 V + 버전 + __ + 설명.sql 규칙으로 파일명을 작성합니다. V1.0__INIT.sql 처럼요. 그럼, Flyway Plugin이 flyway_schema_history 내부 스키마를 이용하여 파일 버전을 관리합니다. 아래 그림과 같이 Shiny DB version 2에서 version 2.1로 버전이 상향되면서 2개의 TABLE이 삭제될 경우, V2.1__refactoring.sql 파일에 DROP TABLE ..을 기술합니다. 
Flyway Plugin은 내부 스키마의 등록된 정보를 기반으로 버전 순서대로 파일 내용을 실행합니다. 가령, version 2는 이미 등록된 정보이고 version 2.1은 신규로 등록된 row의 경우, 해당 스크립트 파일을 실행하여 버전별 이력을 관리하게 됩니다. 이미 row에 등록된 스크립트 버전은 중복으로 실행되지 않습니다. 
여기서 주의할 점은 Flyway로 관리될 SQL 파일은 src/main/resources/db/migration위치에 저장되어야 Flyway Plugin이 인식할 수 있고, Prefix V는 반드시 대문자로 작성되어야 합니다.
Flyway는 JVM에서 동작하는 Application이라면 실행될 때, API를 통해 위 경로에 저장된 SQL파일로 작업을 수행할 수도 있고 Maven으로 Application과 별도로 실행할 수도 있습니다. (Gradle, Ant 모두 지원하나, Maven으로 설명합니다.)
Maven 사용
- 먼저, Flyway 의존성 Library를 다운받습니다. (Spring boot는 Flyway 버전을 관리하므로 버전을 명시하지 않아도 됩니다.)
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway-core.version}</version>
</dependency>
- 단위테스트 실행 시, H2로 Migration을 진행합니다.
public static final String DB_URL =
"jdbc:h2:mem:testdb;MODE=Mysql;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
private static Server server;
@BeforeClass
public static void initDB() throws Exception {
server = Server.createTcpServer();
initSchema();
}
private static void initSchema() {
Flyway flyway = new Flyway();
flyway.setDataSource(DB_URL, "sa", "");
flyway.migrate();
}
@AfterClass
public static void closeDB() throws Exception {
server.shutdown();
}
참고로 Spring Boot는 CoC(Convention over Configuration)에 따라 위의 코드 필요 없이 yaml 파일에 아래의 설정으로 단위 테스트 실행 시 SQL Migration이 가능합니다. (H2 in-memory-db 사용 시, 스키마 형상이 default H2에서 지원하지 않는 keyword일 수 있으므로 MODE 속성으로 사용하는 DBMS를 명시합니다.)
spring:
flyway:
enabled: true
username: sa
datasource:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:testdb;MODE=Mysql;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
Application 실행 시 Migration 진행
- Applicaton main() 메소드에 아래 코드를 추가합니다.
Flyway flyway = Flyway.configure().dataSource("DB_URL", "User", 'Password').load();
flyway.migrate();
Application 실행과는 별도로 Maven Goal 이용하여 Migration 진행
- POM에 Plugin을 추가합니다.
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway-maven-plugin.version}</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
</dependencies>
</plugin>
- flyway.conf 파일에 형상 관리를 반영할 대상 DB 서버 정보 기재 후, Maven으로 Migration을 진행합니다.
flyway.url=jdbc:mysql://toast.com:{Port}/{DB명}
flyway.user=framework
flyway.password=
flyway.ignoreMissingMigrations=true
flyway.validateOnMigrate=false
$ mvn -Dflyway.configFiles=flyway.conf flyway:migrate
마무리
지원 가능한 DB는 다음과 같이 웬만한 DBMS는 거의 지원하므로 개발하시는 DB가 포함되어 있으면 사용을 고려해보세요.
Oracle, SQL Server, SQL Azure, DB2, DB2 z/OS, MySQL (including Amazon RDS), MariaDB,
Google Cloud SQL, PostgreSQL (including Amazon RDS and Heroku), Redshift, Vertica, H2,
Hsql, Derby, SQLite, SAP HANA, solidDB, Sybase ASE, Phoenix...
Migration 파일을 변경 명세 기준으로 작성하게 되면 협업의 경우, 파일이 많아질 수 있으므로 배포 일자가 정해져 있으면 V배포일자_설명.sql(예: V181116_deploy.sql) 파일 하나로 관리하는 것이 협업에도 유용하고 파일이 개수가 커지지 않아 유용합니다. Flyway가 community(무료), Pro, Enterprise(유료) 버전으로 나누어져 있지만, 형상 관리는 community로도 충분히 적용 가능하므로 아직 사용하고 있지 않다면 Human Fault를 줄이고, 단위테스트의 효율을 높일 수 있도록 사용을 검토해보세요. ^^