文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

flywayでDBマイグレーション!:調査編1:簡単な挙動確認

Gradle編が一段落したので、データベースのマイグレーションについて調査していこうと思います〜


f:id:treeapps:20180418131549p:plain

環境は、java・MySQLで開発している場合のマイグレーションする場合についてです。

使用するツール

使用するツールは flyway です。
Flyway by Boxfuse • Database Migrations Made Easy.

1点注意点があって、2013/07/20 現在ではコマンドラインツール版をDLすると404になります。。。
しかし大丈夫です。以下のURLからDLする事ができます。
http://repo1.maven.org/maven2/com/googlecode/flyway/flyway-commandline/2.2/

flywayの実行環境は?

flyway2.2では以下から実行する事ができます。

  • jarをクラスパスに通してjavaから実行。
  • コマンドラインツールから実行。実行用のjar・シェルスクリプト(*.sh)とバッチファイル(*.bat)が同梱。
  • maven pluginでmavenから実行。
  • gradle pluginでgradleから実行。
  • ant taskでantから実行。

結構充実してます。著名なビルドツールからも、javaからも、コマンドラインからも実行可能です。
これだけ対応しているので、どんな環境からでも何かしらの方法で実行できるでしょう。

flywayの制限は?

非常に重要な制限事項があります。それは・・・

ダウングレードに未対応

です。

Flyway does NOT support downgrade scripts.
While the idea of downgrade scripts (popularized by Rails Migrations) is a nice one in theory, unfortunately it breaks down in practice. As soon as you have destructive changes (drop, delete, truncate, ...), you start getting into trouble. And even if you don't, you end up creating home-made alternatives for restoring backups, which need to be properly tested as well.

FAQ - Flyway by Boxfuse • Database Migrations Made Easy.

ロードマップを見ても、ダウングレードには対応する気はないようです。
ダウングレード未対応ということは、旧バージョンのスキーマに戻したい時は、
逐一手動でDBを直してあげないといけないのです。

早速flywayを使ってみる

早速実行して挙動を確認してみます。
今回はお手軽に実行するため、コマンドラインツール版を使います。
デーベースはMySQL、スキーマ名はaddress、としています。

まずはコマンドとオプション一覧を確認

localhost:flyway tree$ ./flyway
Flyway (Command-line Tool) v.2.2

********
* Usage
********

flyway [options] command

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

Commands
========
clean    : Drops all objects in the configured schemas
init     : Creates and initializes the 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 (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
sqlMigrationPrefix     : File name prefix for Sql migrations
sqlMigrationSuffix     : File name suffix for Sql migrations
encoding               : Encoding of Sql migrations
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 migrate
outOfOrder             : Allows migrations to be run "out of order"
validateOnMigrate      : Validate when running migrate
cleanOnValidationError : Automatically clean on a validation error
initVersion            : Version to tag schema with when executing init
initDescription        : Description to tag schema with when executing init
initOnMigrate          : Init 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)
jarDir                 : Dir for Jdbc drivers & Java migrations (default: jars)

Add -X to print debug output

Example
=======
flyway -target=1.5 -placeholder.user=my_user info

More info at http://flywaydb.org/documentation/commandline

こんな感じです。
ここでオプションに注目。
driverのurlが引数で指定できる!のです。
これはつまり、ローカル環境はlocalhost、本番環境は任意のホスト名、という具合に簡単に接続先を変えられます。

infoコマンドでマイグレーションの状態確認

localhost:flyway tree$ ./flyway info
Flyway (Command-line Tool) v.2.2

+----------------+----------------------------+---------------------+---------+
| Version        | Description                | Installed on        | State   |
+----------------+----------------------------+---------------------+---------+
| No migrations found                                                         |
+----------------+----------------------------+---------------------+---------+

マイグレーションが見つからないそうです。

migrateしてみる

何も考えずにいきなりマイグレートしてみます。

localhost:flyway tree$ ./flyway migrate
Flyway (Command-line Tool) v.2.2

ERROR: FlywayException: Found non-empty schema `address` without metadata table! Use init() first to initialize the metadata table.
ERROR: Occured in com.googlecode.flyway.core.Flyway$1.execute() at line 848

addressスキーマはinitされてない!と怒られました。
ERRORの2行目は完全に内部のデバッグ用のエラーですかね。
使う側からしたら2行目のERRORが表示されても、何のことやら?これ見て何したらいいの?ってなります。

initしてみる

localhost:flyway tree$ ./flyway init
Flyway (Command-line Tool) v.2.2

Creating Metadata table: `address`.`schema_version`
Schema initialized with version: 1

お、成功したようです。
バージョン1でschema_versionテーブル作ったぜ!と表示されました。
早速DBを見てみます。

mysql> show tables;
+-------------------+
| Tables_in_address |
+-------------------+
| address           |
| schema_version    |
+-------------------+
2 rows in set (0.00 sec)

mysql> show create table schema_version \G
*************************** 1. row ***************************
       Table: schema_version
Create Table: CREATE TABLE `schema_version` (
  `version_rank` int(11) NOT NULL,
  `installed_rank` int(11) NOT NULL,
  `version` varchar(50) NOT NULL,
  `description` varchar(200) NOT NULL,
  `type` varchar(20) NOT NULL,
  `script` varchar(1000) NOT NULL,
  `checksum` int(11) DEFAULT NULL,
  `installed_by` varchar(100) NOT NULL,
  `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `execution_time` int(11) NOT NULL,
  `success` tinyint(1) NOT NULL,
  PRIMARY KEY (`version`),
  KEY `schema_version_vr_idx` (`version_rank`),
  KEY `schema_version_ir_idx` (`installed_rank`),
  KEY `schema_version_s_idx` (`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql> select * from schema_version;
+--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+
| version_rank | installed_rank | version | description       | type | script            | checksum | installed_by | installed_on        | execution_time | success |
+--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+
|            1 |              1 | 1       | << Flyway Init >> | INIT | << Flyway Init >> |     NULL | root         | 2013-07-20 00:20:15 |              0 |       1 |
+--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+
1 row in set (0.00 sec)

バージョン1でschema_versionテーブルにデータが挿入されてますね。
どうやら以下が記録されるようです。

  • version:現在のバージョン。
  • description:ファイル名の「V2__description.sql」のdescription部分が設定されます。
  • type:実行にかかった時間。
  • script:実行したsqlのファイル名。
  • installed_by:実行した時のmysqlのユーザ名。
  • installed_on:実行した時の時間。
  • execution_time:マイグレーションにかかった時間。
  • success:1=true、0=false。MySQLにはbool型が無いので数値なのでしょう。

initした後に更にinitしてみる

いじわるしてみます。

localhost:flyway tree$ ./flyway init
Flyway (Command-line Tool) v.2.2

Metadata table `address`.`schema_version` already initialized with (1,<< Flyway Init >>). Skipping.
Schema initialized with version: 1

おお、「addressスキーマは既にinitされてんぞコラ!」と怒られました。
誤って2回initしたら壊れる、という悲劇は起こらなそうですね。

sqlを用意してmigrateコマンドを実行

用意するファイルは「$FLYWAY_HOME/sql/V1__test.sql」です。
とりあえずファイルの中身は空っぽでよいです。

localhost:flyway tree$ ./flyway migrate
Flyway (Command-line Tool) v.2.2

Current version of schema `address`: 1
Schema `address` is up to date. No migration necessary.

あれ?マイグレーションの必要はないぜ!と言われました。
ここでバージョンに注目。バージョンは1ですね。
で、マイグレーションファイルのバージョンも1です。
バージョンが1なら、マイグレーションファイルのバージョン1は既に実行された事になります。
だからV1__test.sqlは既に実行済みだから、No migration necessary. なのです。

ということで、初回のマイグレーションのファイル名は「V2__xxx.sql」でなければ実行されないのです。
気分的にはファイル名がV2から始まるのは嫌ですね。。V1は無いの?と思ってしまいます。

V2のsqlを用意してmigrate

現在のマイグレーションのバージョンは1なので、V2のsql、V2__test.sqlを用意します。
ここで少しいじわるして、シンタックスエラーするsqlにしておきます。

localhost:sql tree$ cat V2__test.sql
hoge

ではmigrateしてみます。

localhost:flyway tree$ ./flyway migrate
Flyway (Command-line Tool) v.2.2

Current version of schema `address`: 1
Migrating schema `address` to version 2
ERROR: FlywayException: Migration of schema `address` to version 2 failed! Please restore backups and roll back database and code!
ERROR: Occured in com.googlecode.flyway.core.command.DbMigrate.migrate() at line 199
ERROR: Caused by com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'hoge' at line 1
ERROR: Occured in sun.reflect.NativeConstructorAccessorImpl.newInstance0() at line -2

mysql> mysql> select * from schema_version;
+--------------+----------------+---------+-------------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+
| version_rank | installed_rank | version | description       | type | script            | checksum    | installed_by | installed_on        | execution_time | success |
+--------------+----------------+---------+-------------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+
|            1 |              1 | 1       | << Flyway Init >> | INIT | << Flyway Init >> |        NULL | root         | 2013-07-20 00:20:15 |              0 |       1 |
|            2 |              2 | 2       | test              | SQL  | V2__test.sql      | -1959140262 | root         | 2013-07-20 00:41:58 |              7 |       0 |
+--------------+----------------+---------+-------------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+

シンタックスエラーが検出されました。
バージョン2の実行は失敗したから、sqlファイルを修正してDBをrollbackしてね、と言われました。
schema_versionテーブルには、失敗したmigrateが記録されています。successが0になってますね。

repairコマンドで修復してみる

前回わざと失敗してみたので、repairコマンドで修復を試みます。
修復って何?と思ったので、挙動を注意して確認してみます。

localhost:flyway tree$ ./flyway repair
Flyway (Command-line Tool) v.2.2

Metadata table `address`.`schema_version` successfully repaired (execution time 00:00.000s).
Manual cleanup of the remaining effects the failed migration may still be required.

mysql> select * from schema_version;
+--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+
| version_rank | installed_rank | version | description       | type | script            | checksum | installed_by | installed_on        | execution_time | success |
+--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+
|            1 |              1 | 1       | << Flyway Init >> | INIT | << Flyway Init >> |     NULL | root         | 2013-07-20 00:20:15 |              0 |       1 |
+--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+
1 row in set (0.01 sec)

リペアに成功しました。repairすると、success=0のデータがdeleteされ、
V2のマイグレーションが無かった事にされるようです。
これはつまり、repairでバージョンダウン、sqlファイルを修正、再度V2でmigrate、とする事を想定しているようです。
なるほどなるほど。しかしDBの状態は元に戻らないので、
「Manual cleanup of the remaining effects the failed migration may still be required.」
と言われます。
失敗したマイグレーションsqlはflyway側で自動修復できんから、手動で自力で直してね♪って事でしょうか。
alter tableのような暗黙のコミットが走るコマンドがsqlに書かれていた場合、
トランザクションが効かないので、部分的にsqlが適用される場合があるから、それは自分で直してね、って事ですね。

repairした後に更にrepairしてみる

恒例のいじわるです。2回連続repairしたらどうなるか。

localhost:flyway tree$ ./flyway repair
Flyway (Command-line Tool) v.2.2

Repair of metadata table `address`.`schema_version` not necessary. No failed migration detected.

おお、マイグレーション対象は無い、と言われましたね。
なんかメッセージがおかしい気がします。「リペア対象が無い」が正解なんじゃ・・・?

この挙動を見ると、直前バージョンのsuccess=0のみがrepairの対象となるようです。
これなら事故が怒らなくて安心です。

正しいsqlを用意してmigrate

ようやく正常系の動作です。
V2__test.sqlには以下のsqlを記述します。

drop table if exists hoge;
create table hoge (
    id int
)engine=innodb charset=utf8mb4;

ではmigrateを実行。

localhost:flyway tree$ ./flyway migrate
Flyway (Command-line Tool) v.2.2

Current version of schema `address`: 1
Migrating schema `address` to version 2
Successfully applied 1 migration to schema `address` (execution time 00:00.077s).

mysql> select * from schema_version;
+--------------+----------------+---------+-------------------+------+-------------------+------------+--------------+---------------------+----------------+---------+
| version_rank | installed_rank | version | description       | type | script            | checksum   | installed_by | installed_on        | execution_time | success |
+--------------+----------------+---------+-------------------+------+-------------------+------------+--------------+---------------------+----------------+---------+
|            1 |              1 | 1       | << Flyway Init >> | INIT | << Flyway Init >> |       NULL | root         | 2013-07-20 00:20:15 |              0 |       1 |
|            2 |              2 | 2       | test              | SQL  | V2__test.sql      | 1679364319 | root         | 2013-07-20 15:42:27 |             23 |       1 |
+--------------+----------------+---------+-------------------+------+-------------------+------------+--------------+---------------------+----------------+---------+
2 rows in set (0.00 sec)

mysql> show create table hoge\G
*************************** 1. row ***************************
       Table: hoge
Create Table: CREATE TABLE `hoge` (
  `id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

これが正常系です。ただしくhogeテーブルも作成されました。

success=1を手動削除したら強制バージョンダウンできる?

ちょっと試してみました。
success=1のデータはマイグレーションに成功した事を意味しますが、これをdeleteしたら、
1個前のバージョンをマイグレーションできるか?

できました

flywayがschema_version以外のメタデータを保持したりしないから可能なのでしょう。

長くなってしまったので、今回の調査はここまでです。