DBマイグレーションツール活用のすすめ〜Flyway〜

  • 43
    Like
  • 0
    Comment
More than 1 year has passed since last update.

概要

巷にはDBマイグレーションツールなるものがあります。この、マイグレーションというのは移行を意味しますが、いわゆる旧環境から新環境への移行といったデータ移行を意味するものではないのです。DB環境(DBスキーマ+データ)を移行して、同一状態のDB環境を手軽に構築できるようするツールなのです。

ここではFlywayというDBマイグレーションツールを試してみます。DB環境をバージョン管理して、DB移行を簡単にしてくれるツールです。

開発時、複数の開発者がそれぞれのDBを使うことがあります。それらのDB環境を一致させることが簡単になるので、古いDB環境を使って間違った実装・テストをするといった事態を未然に防止できるようになります。

ソースコードのバージョン管理が開発を支えるように、DB環境のバージョン管理も開発を支えてくれるはず。今後、システム開発の現場でDBマイグレーションツールの利用を検討する機会になれば嬉しい、という思いを込めて、メモしておきます。(少なくとも自分自身の備忘録にはなるはず)

Flyway + H2 + Gradle で試す

手軽にFlywayを試してみて、そのパワーを感じてみます。DBにH2を使い、gradle経由でFlywayを使います。通常開発でFlyway単発で使うことは少ないでしょうから。

環境

  • OS-X (ここでは。WindowsでもCentOSでもいける。)
  • Java(最新バージョン入れといたら間違いない。)
  • H2(もちろんMySQLとかでもよい)

準備

このメモで完結できるよう、インストールについてもここに書いておきます。
インストールと言ってもアーカイブを展開するだけ。

  1. H2のインストール
  2. Gradleのインストール

1.H2のインストール

H2はJavaでできたRDBエンジン。HPより最新版をダウンロードする。2015/12/2時点の最新バージョンは1.4.190。
適当なフォルダに展開して、h2/bin/h2.sh を実行すると、ブラウザが起動し、H2 Console が開く。

なおOS-X環境だと、上記ファイルを実行すると正しく動かない。

$ ./h2.sh
zsh: ./h2.sh: bad interpreter: /bin/sh^M: no such file or directory

改行コードがDOS(CR+LF)になっているから。
UNIX(LF)にしてやるとうまくいく。

$ cp h2.sh h2.sh.org
$ tr -d ¥¥r < h2.sh.org > h2.sh
$ chmod +x h2.sh

スクリーンショット 2015-12-01 1.27.54.png

2.Gradleのインストール

gradleのHPからアーカイブをダウンロード。Binary only distributionでよい。2012/12/1現在の最新バージョンは2.9。
それを適当なフォルダに展開する。

個人的にはsdkmanを使ったやり方で入れてる。
OS-Xであればbrewでインストールするのが簡単かも。

ここまでで準備は完了。

Flywayを扱えるプロジェクトを作る

Gradleでプロジェクトを作る

まず、gradle initを実行してプロジェクトを作る。

$ gradle init --type java-library
Starting a new Gradle Daemon for this build (subsequent builds will be faster).
:wrapper
:init

BUILD SUCCESSFUL

Total time: 7.385 secs

こんな感じでフォルダが生成される。

$ du
176 ./.gradle/2.9/taskArtifacts
176 ./.gradle/2.9
176 ./.gradle
120 ./gradle/wrapper
120 ./gradle
8   ./src/main/java
8   ./src/main
8   ./src/test/java
8   ./src/test
16  ./src
352 .

build.gradleファイルにflyway関連ライブラリを組み込む

カレントディレクトリに、build.gradleというファイルができる。これを修正してflywayライブラリを組み込む。
修正後のbuild.gradleは以下のような感じ。

mavenCnetral()でなくjcenter()でもいいはず。
生成されたコメントは消してます。

build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.h2database:h2:1.4.190'
        classpath 'org.flywaydb:flyway-gradle-plugin:3.2.1'
    }
}

apply plugin: 'java'
apply plugin: 'org.flywaydb.flyway'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.slf4j:slf4j-api:1.7.13' // init時のもの
    testCompile 'junit:junit:4.12' // init時のもの
}

flyway {
    user = 'sa'
    //url = 'jdbc:h2:./dummy' // Embedded
    url = 'jdbc:h2:tcp://localhost/~/dummy' // c/s
}

うまく設定できているかを確認してみる。gradle tasksを実行して、flywayタスクがあればとりあえずOK。

flyway tasks
------------
flywayBaseline - Baselines an existing database, excluding all migrations up to and including baselineVersion.
flywayClean - Drops all objects in the configured schemas.
flywayInfo - Prints the details and status information about all the migrations.
flywayInit - Baselines an existing database, excluding all migrations up to and including baselineVersion.
flywayMigrate - Migrates the schema to the latest version.
flywayRepair - Repairs the Flyway metadata table.
flywayValidate - Validates the applied migrations against the ones available on the classpath.

Flywayを試す

マイグレーション用のSQLファイルを追加する

まずmigrationフォルダを作る。

$ mkdir -p src/main/resources/db/migration

次に初期DBを作成するSQLスクリプトを作る。先ほどのmigrationフォルダに、V1__Initial_DB.sqlというファイルを作成する。中身はこんな感じにする。

V1__Initail_DB.sql
CREATE TABLE USER (
 ID LONG NOT NULL IDENTITY,
 USER_ID VARCHAR(10) NOT NULL,
 PASSWORD VARCHAR(20) NOT NULL,
 NAME VARCHAR(100) NOT NULL
);

マイグレーションファイルは命名規則が決まっている。
V1_説明.sql というフォーマット。
最初にプレフィックスが
V 。これがデフォルト。変更できる。
次にバージョン番号。数値。1.1や1_2_3などとかける。
次が区切り文字。
_ これは固定。
次が説明文。日本語OK。
最後にサフィックス。デフォルトは.sql。変更できる。

マイグレーション状況を確認する

gradle flywayInfo と入力し、DBの状態を確認する。
先ほど作成したマイグレーションファイルの情報が表示される。

$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on        | State   |
+---------+-------------+---------------------+---------+
| 1       | Initial DB  |                     | Pending |
+---------+-------------+---------------------+---------+


BUILD SUCCESSFUL

Total time: 0.516 secs

H2 Consoleを確認。特にDBは変化なし。

スクリーンショット 2015-12-01 1.36.07.png

マイグレーションする

gradle flywayMigrate と入力しマイグレーショする。

$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate

BUILD SUCCESSFUL

Total time: 0.531 secs

gradle flywayInfo で確認。

$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on        | State   |
+---------+-------------+---------------------+---------+
| 1       | Initial DB  | 2015-12-01 01:39:15 | Success |
+---------+-------------+---------------------+---------+


BUILD SUCCESSFUL

Total time: 0.523 secs

StateがPengingからSuceessになる。

H2 ConsocleでDBを確認する。USERテーブルとschema_versionテーブルができている。
スクリーンショット 2015-12-01 1.41.18.png

なお、schema_versionテーブルはこんな感じ。
スクリーンショット 2015-12-01 1.43.01.png

さらにマイグレーションする

次のバージョン、V1.1__User_Data.sql を追加してみる。

V1.1_User_Data.sql
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST01', 'TEST', 'テストユーザー01');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST02', 'TEST', 'テストユーザー02');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST03', 'TEST', 'テストユーザー03');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST04', 'TEST', 'テストユーザー04');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST05', 'TEST', 'テストユーザー05');

gradle flywayInfo で確認。

$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on        | State   |
+---------+-------------+---------------------+---------+
| 1       | Initial DB  | 2015-12-01 01:39:15 | Success |
| 1.1     | User Data   |                     | Pending |
+---------+-------------+---------------------+---------+


BUILD SUCCESSFUL

Total time: 0.537 secs

Version 1.1 がPenging状態。
gradle flywayMigrate して、gradle flywayInfo する。

$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate

BUILD SUCCESSFUL

Total time: 0.515 secs
$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on        | State   |
+---------+-------------+---------------------+---------+
| 1       | Initial DB  | 2015-12-01 02:06:10 | Success |
| 1.1     | User Data   | 2015-12-01 02:07:04 | Success |
+---------+-------------+---------------------+---------+


BUILD SUCCESSFUL

Total time: 0.513 secs

DBを見てみる。
スクリーンショット 2015-12-01 2.09.21.png

こんな感じでDB環境のバージョン管理をしていく。

その他

マイグレーション失敗時の対処方法

マイグレーションファイルにミスがあると当然のことながらマイグレーションに失敗する。
たとえば、こんなマイグレーションファイルを用意する。

V1.2__Modify_User.sql
ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA; // 本当はDATE

gralde flywayMigrateとしてみる。すると、、、

$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate
Migration of schema "PUBLIC" to version 1.2 failed! Please restore backups and roll back database and code!
:flywayMigrate FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':flywayMigrate'.
> Error occurred while executing flywayMigrate

  Migration V1.2__Modify_User.sql failed
  --------------------------------------
  SQL State  : HY004
  Error Code : 50004
  Message    : 不明なデータ型: "DATA"
  Unknown data type: "DATA"; SQL statement:
  ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA [50004-190]
  Location   : db/migration/V1.2__Modify_User.sql (/Users/hagi/tmp/flyway/build/resources/main/db/migration/V1.2__Modify_User.sql)
  Line       : 1
  Statement  : ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA

  不明なデータ型: "DATA"
  Unknown data type: "DATA"; SQL statement:
  ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA [50004-190]

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 0.555 secs

gradle flywayInfoとすると、こんな感じになる。

$ gradle flywayInfo                                                                                                       x [1 22:49:07]
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on        | State   |
+---------+-------------+---------------------+---------+
| 1       | Initial DB  | 2015-12-01 22:46:53 | Success |
| 1.1     | User Data   | 2015-12-01 22:46:53 | Success |
| 1.2     | Modify User | 2015-12-01 22:49:07 | Failed  |
+---------+-------------+---------------------+---------+


BUILD SUCCESSFUL

Total time: 0.552 secs

Version1.2のマイグレーションのStateがFailedとなる。
こうなったら、gradle flywayRepairとしてPendingに戻し、マイグレーションファイルを修正し、再度gradle flywayMigrateしてやる。

マイグレーションファイルのバリデーションについて

マイグレーション済みのV1.1__User_Data.sqlを変更して、gradle flywayValidateをすると、こんな感じでやっちまったなぁ、となる。
DBに適用済みのバージョンとローカルにあるマイグレーションファイルが異なることを教えてくれる。

$ gradle flywayValidate
:compileJava UP-TO-DATE
:processResources
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:flywayValidate FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':flywayValidate'.
> Error occurred while executing flywayValidate
  Validate failed. Migration Checksum mismatch for migration 1.1
  -> Applied to database : -1549820110
  -> Resolved locally    : -702322096

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 0.532 secs

この場合は、V1.1__User_Data.sqlを元に戻すか、再度、V1からマイグレーションすればいい。

最後に

Flywayをうまく使えばシステム開発で必須のDB環境の管理が楽になる。なまじSQLのスキルがあるとテーブルを直接いじったりして、SQLスクリプトとして残らないことがある。たとえば、ALTER TABEを直接実施して環境を調整してしまうとか。これを良しとしていると、DB環境の再現性があやしくなる。

最近やってたあのプロジェクトでそんな問題に遭遇したなぁ。
今やってるあのプロジェクトに導入しておきたいなぁ。

参考