Go言語で実装したWebアプリケーションサイトのDBマイグレーションツールとしてgolang-migrate/migrateを使っていて、そこそこ便利につかえているので紹介してみたいと思います。
migrateを使うメリット
migrateは、Go言語で作成されたDBマイグレーションツールの1つです。
migrateを使うことで得られるメリットは
- スキーマが壊れにくい
- 正しい順番でマイグレーションを行うことができる。
- スキーマ変更を効率的に行える
- 定義ファイルを作ることで複数の環境で同じマイグレーションを再現できる。
- 学習コストが低い
- go環境のマイグレーションツールは、他に goose や sql-migrate などがある。
- これらは定義ファイルに
-- +goose Up
などの独自の記法で記載するが、migrateはそれがないので覚えることが少ない。
の3つかと思います。
migrateを使うことにした背景
golang のORMフレームワークに gorm を使っているのですが、CLIが提供されていないためDBマイグレーションのタイミングをコントロールしづらいので、 migrate
でカバーすることにしました。
migrate
を採用する際の注意点としては、gormは独自のDSLでスキーマ定義を記載することでDBシステム間(MySQLやPostgreSQL等)の差異を吸収してくれますが、 migrate
の場合は定義ファイルに書かれたDDLを実行するだけなので、DBシステムの差異を意識して定義ファイルを記載する必要があります。
環境/前提
- Ubuntu 18.04 LTS デスクトップ
- PostgreSQLインストール済
- PostgreSQLに"sample"という名前のデータベース作成済
インストール
インストール方法が、README.mdにもGETTING_STARTED.md にも書いていないので、分かりづらいですが以下に記載されています(2021/1/13時点)。今後READMEに移動するかもしれないので、都度最新の情報を確認してください。
https://github.com/golang-migrate/migrate/tree/master/cmd/migrate
ビルド済のバイナリをインストールする方法やapt
でもインストールできますが、今回はgo get
でインストールしました。
$ go get -tags 'postgres' -u github.com/golang-migrate/migrate/cmd/migrate
使ってみる
最初は以下のPostgreSQLのチュートリアルをやってみるのがてっとり早いかと思います。 migrate/TUTORIAL.md at master · golang-migrate/migrate · GitHub
マイグレーションの実行に失敗したらどうなるか等把握するために色々試してみたいので、チュートリアルにの内容にちょっと手を加えて進めてみます。
マイグレーションの定義ファイルを作る
1つ目のマイグレーションとして、ユーザーテーブルを作って、インデックスを貼ってみたいと思います。
を指定して migrate create
を実行すると、000001_create_users.(up/down).sql
という2つのSQLファイルができます。
$ migrate create -ext sql -dir db/migrations -seq create_users ~/golang-migrate-sample/db/migrations/000001_create_users.up.sql ~/golang-migrate-sample/db/migrations/000001_create_users.down.sql
作成されたup/down.sqlファイルにそれぞれ以下のようにCREATE TABLE
とDROP TABLE
のDDLを書いてみます。PostgreSQLはDDLでもトランザクションが使えるので使ってみます。
BEGIN; CREATE TABLE IF NOT EXISTS users( id serial PRIMARY KEY, username VARCHAR (50) UNIQUE NOT NULL, age INT NOTNULL ); CREATE INDEX on users (age); COMMIT;
DROP TABLE IF EXISTS users;
同様に、コメントテーブルのマイグレーションも書いてみます。
$ migrate create -ext sql -dir db/migrations -seq create_comments ~/golang-migrate-sample/db/migrations/000002_create_comments.up.sql ~/golang-migrate-sample/db/migrations/000002_create_comments.down.sql
CREATE TABLE IF NOT EXISTS comments( id INT PRIMARY KEY, body VARCHAR (50) NOT NULL, user_id INT NOT NULL );
DROP TABLE IF EXISTS comments;
ファイル/ディレクトリ構成確認
ファイル/ディレクトリ構成としては以下のようになっている状態かと思います。
└── db └── migrations ├── 000001_create_users.down.sql ├── 000001_create_users.up.sql ├── 000002_create_comments.down.sql └── 000002_create_comments.up.sql
マイグレーションの実行
現状は以下の図のように、1と2の定義ファイルがあるだけでDBにはテーブルが1つもない状態です。
さっそくマイグレーションを実行してみたいと思います。
migrate up 1
を実行し、ユーザーテーブルをCREATEする
PostgreSQLの接続URLを環境変数に入れて、バージョンを1つ上げてみます。
$ export POSTGRESQL_URL='postgres://postgres:password@localhost:5432/sample?sslmode=disable' $ migrate -path db/migrations -database ${POSTGRESQL_URL} up 1
以下の図のように、000001_create_users.up.sql
だけが実行され、schema_migrations
テーブル(version: 1のレコードが1件)とusersテーブル(内容は空)ができました。
マイグレーションを実行するとschema_migrations
テーブルに
- 現在のバージョン(version)
- エラーが発生すると
dirty: true
、しなければdirty: false
が保存されます。
migrate down 1
を実行し、ユーザーテーブルをDROPする
同様にバージョンを1つ下げてみます。
$ migrate -path db/migrations -database ${POSTGRESQL_URL} down 1
すると以下の図のようにusersテーブル、とschema_migrationsテーブルのレコードがなくなり、DROPできたことが確認できます。
わざと失敗してみる
先程の000001_create_users.up.sql
を以下のように修正し、CREATE TABLE
はそのままでCREATE INDEX
は失敗するようにしてみます。
BEGIN; CREATE TABLE IF NOT EXISTS users( id serial PRIMARY KEY, username VARCHAR (50) UNIQUE NOT NULL, age INT NOTNULL ); CREATE INDEX on users, (age); COMMIT;
$ migrate -path db/migrations -database ${POSTGRESQL_URL} up 1 error: migration failed: ","またはその近辺で構文エラー (column 22) in line 7: BEGIN; CREATE TABLE IF NOT EXISTS users( id INT PRIMARY KEY, username VARCHAR (50) UNIQUE NOT NULL, age INT NOT NULL ); CREATE INDEX on users, (age); COMMIT; (details: pq: ","またはその近辺で構文エラー)
すると、以下のようにトランザクションが効いてユーザーテーブルが作られずに、 schema_migrations
の dirty
が true
になることが確認できます。
dirty: trueの状態を修正する方法をドキュメントから探していると force
を指定することで、dirty
が false
の状態(1つ前のバージョン)に強制的に変更できるという記載があったのでそれをやろうとしましたが、こちらのissueにバージョン1で失敗した場合はバージョン0にはできないと書いてあったので、仕方なく schema_migrations
テーブルは手動で削除しました。
バージョン2以降で失敗した場合には、以下のように force バージョン
を指定することで、 schema_migrations
を varsion: 1, dirty: false
の状態にできます。
$ migrate -path db/migrations -database ${POSTGRESQL_URL} force 1
migrate up
を実行し、ユーザーテーブルとコメントテーブルを作成
最後にmigrate up
実行することで、未実行のマイグレーションが全て実行されることを確認しました。000001_create_users.up.sql
の修正は元に戻して実行しています。
$ migrate -path db/migrations -database ${POSTGRESQL_URL} up 1/u create_users (25.104164ms) 2/u create_comments (53.025987ms)
使ってみた所感
ちょっとしたハマりどころもあるので、そんなによく作り込まれているという感じではなく万人受けはしなさそうだけど、インタフェースがシンプル(up、down、forceくらい)なので使い勝手がよく
- 小規模のシステム
- 個人開発
といった使用用途には良い気がしました。少なくとも個人開発で使っている分には困るケースには当たってないです。
サンプル
作ったサンプルのURLを載せておきますので、よかったら参考にしてみてください! https://github.com/moritamori/golang-migrate-sample