golang-migrateでDBマイグレーション

2020.05.20

golang-migrateとは?

Go言語で作成されたDBマイグレーションツールで、GO言語のライブラリとしてだけではなく、CLIからも利用できます。そのためJavaで開発されているprismatixというサービスでも利用することができます。

JavaのマイグレーションツールにFlywayliquibaseといったものがあります。その中でこのツールの特徴は、マイグレーションを定義しているファイルをGitHubやAWS S3などから直接参照することができることです。(もちろんローカルのファイルシステムにも対応しています) また対応しているデータベースエンジンも多く、MySQLやPostgreSQL以外にもMongoDBなどにも対応しているなど、様々な場面で使うことができるツールだと思います。

環境構築

今回は手軽に試すためにdocker上で、golang-migrateとPostgreSQLを動作させて検証していきます。

( golang-migrateの公式Docker image 1がありますが、セットアップが難しくないため今回は手動でインストールします。)

1. PostgreSQLのコンテナを起動

% docker run --rm -it -p 5432:5432 -e POSTGRES_PASSWORD=password \
             --name postgres postgres

[Note]

  • コンテナに名前をつけて起動します
  • 引数の環境変数 POSTGRES_PASSWORDpostgres ユーザのパスワードを設定します

2. golang-migrateをインストールするコンテナを起動

以降はこのコンテナの中で作業します。

% docker run --rm -it --link postgres:postgres ubuntu:18.04 bash

[Note]

  • --link にPostgreSQLのコンテナ名を指定し、DBにコンテナ内から ホスト名 postgres でアクセスできるようにします

3. golang-migrateをインストール

$ apt-get update && apt-get upgrade -y
$ apt-get install -y curl gnupg2 vim
$ curl -L https://github.com/golang-migrate/migrate/releases/download/v4.11.0/migrate.linux-amd64.tar.gz | tar xvz
$ mv ./migrate.linux-amd64 /usr/bin/migrate

[Note]

  • 執筆時点の最新版 v4.11.0 にて以降の検証を行っています

4. 動作確認用のpostgres-clientをインストール

PostgreSQLの公式にインストール手順2がありますので、それに従ってPostgreSQLのクライアントをインストールします。

$ echo 'deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > /etc/apt/sources.list.d/pgdg.list
$ curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
$ apt-get update
$ apt-get install -y postgresql-client-12

使ってみよう

golang-migrateをインストールして、公式のPostgreSQLのチュートリアルをやっていきます。

1. example データベースを作成

PostgreSQLにマイグレーションを行うための example データベースを作成します。

$ createdb -h postgres -U postgres example

2. マイグレーションのテンプレートを作成

golang-migrate を使ってマイグレーションファイルのテンプレートを生成します。今回は migrations/example1 ディレクトリに create_users_table のファイル名で生成します。

$ migrate create -ext sql -dir migrations/example1 -seq create_users_table
/migrations/example1/000001_create_users_table.up.sql
/migrations/example1/000001_create_users_table.down.sql

3. 作成されたテンプレートにそれぞれSQLを記載

作成された2つファイルは、更新用のSQLファイルと切り戻し用のSQLファイルを定義します。

000001_create_users_table.up.sql に下記を記載します (更新用)

CREATE TABLE IF NOT EXISTS users(
    user_id serial PRIMARY KEY,
    username VARCHAR (50) UNIQUE NOT NULL,
    password VARCHAR (50) NOT NULL,
    email VARCHAR (300) UNIQUE NOT NULL
);

000001_create_users_table.down.sql に下記を記載します (切り戻し用)

DROP TABLE IF EXISTS users;

4. 1回目のマイグレーション実施

golang-migrate でマイグレーションを行います。

$ export POSTGRESQL_URL='postgres://postgres:password@postgres:5432/example?sslmode=disable'
$ migrate -database ${POSTGRESQL_URL} -path migrations/example1 up
1/u create_users_table (13.397ms)

5. exampleにマイグレーションされているか確認

PostgreSQLのクライアントでデータベースに反映されているか確認してみます。

$ psql -h postgres -U postgres example
example=# \d
List of relations
Schema | Name | Type | Owner
-------+-------------------+----------+----------
public | schema_migrations | table | postgres
public | users | table | postgres
public | users_user_id_seq | sequence | postgres
(3 rows)

example=# \d users
Table "public.users"
Column | Type | Collation | Nullable | Default
---------+------------------------+-----------+----------+-------------------
user_id | integer | | not null | nextval('users_user_id_seq'::regclass)
username | character varying(50) | | not null |
password | character varying(50) | | not null |
email | character varying(300) | | not null |
Indexes:
    "users_pkey" PRIMARY KEY, btree (user_id)
    "users_email_key" UNIQUE CONSTRAINT, btree (email)
    "users_username_key" UNIQUE CONSTRAINT, btree (username)

example=# \q

usersテーブルと users_user_id_seqシーケンスが作成されており、カラムも作成されているので成功しています。

[Note]

  • schema_migrations はgolang-migrateが履歴管理などに利用しているテーブルです

6. 2回目のマイグレーションのSQLファイル作成

1回目と同様にテンプレートを作成し、編集を行ってマイグレーションします。

今回はマイグレーション中にトランザクションを使用します。

$ migrate create -ext sql -dir migrations/example1 -seq add_mood_to_users
/migrations/example1/000002_add_mood_to_users.up.sql
/migrations/example1/000002_add_mood_to_users.down.sql

それぞれ編集します。

000002_add_mood_to_users.up.sql に下記を記載します (更新用)

BEGIN;

CREATE TYPE enum_mood AS ENUM (
    'happy',
    'sad',
    'neutral'
);
ALTER TABLE users ADD COLUMN mood enum_mood;

COMMIT;

000002_add_mood_to_users.down.sql に下記を記載します (切り戻し用)

BEGIN;

ALTER TABLE users DROP COLUMN mood;
DROP TYPE enum_mood;

COMMIT;

6. 2回目のマイグレーション実施・確認

1回目と同様にマイグレーションを実行し、確認します。

$ migrate -database ${POSTGRESQL_URL} -path migrations/example1 up
2/u add_mood_to_users (9.1277ms)

$ psql -h postgres -U postgres example
example=# \d users
Table "public.users"
Column | Type | Collation | Nullable | Default
---------+------------------------+-----------+----------+-------------------
user_id | integer | | not null | nextval('users_user_id_seq'::regclass)
username | character varying(50) | | not null |
password | character varying(50) | | not null |
email | character varying(300) | | not null |
mood | enum_mood | | |
Indexes:
    "users_pkey" PRIMARY KEY, btree (user_id)
    "users_email_key" UNIQUE CONSTRAINT, btree (email)
    "users_username_key" UNIQUE CONSTRAINT, btree (username)

example=# \q

無事にマイグレーションされていることが確認できました。

[応用編1] GitHubからマイグレーションファイルを取得してみよう

公式のGitHubにはSQLのexample があります。今回はそれを利用させていただき、認証やタグ指定などの確認を行うためにプライベートリポジトリへ次のように作成しました。

ツールに指定するGitHubのURIを変更することで、特定のブランチやタグを取得することができるため試します。

ブランチツリー

* f34fc01 (HEAD -> master, origin/master, origin/HEAD) add new file ($$$$)
| * 735dddc (origin/develop, develop) Add files via upload (#)
|/
* 4ae8484 (tag: v0.2.0) Add files via upload ($$$)
* 23b249b (tag: v0.1.0) Add files via upload ($$)
* 8435828 Add files via upload ($)
* 2eaa8b3 Create README.md

リポジトリ内構造

ファイル名末尾に記載されているの $ # の記号はブランチツリーの各コミットと対応させてありますので、以降の手順でマイグレーションされているファイルが正しいかの確認にご利用ください。

├── README.md
└── migrations
    ├── 1085649617_create_users_table.down.sql       ($)
    ├── 1085649617_create_users_table.up.sql          ($)
    ├── 1185749658_add_city_to_users.down.sql         ($$)
    ├── 1185749658_add_city_to_users.up.sql           ($$)
    ├── 1285849751_add_index_on_user_emails.down.sql  ($$)
    ├── 1285849751_add_index_on_user_emails.up.sql    ($$)
    ├── 1385949617_create_books_table.down.sql        ($$)
    ├── 1385949617_create_books_table.up.sql          ($$)
    ├── 1485949617_create_movies_table.down.sql       ($$$)
    ├── 1485949617_create_movies_table.up.sql         ($$$)
    ├── 1585849751_just_a_comment.up.sql              ($$$)
    ├── 1685849751_another_comment.up.sql             ($$$)
    ├── 1785849751_another_comment.up.sql             (#)
    ├── 1885849751_another_comment.up.sql             (#)
    └── 1900000000_create_sample_table.up.sql         ($$$$)

[Note]

  • GitHubを利用した場合のURIのフォーマットは githubをご覧ください

URIにブランチなどの指定なしの場合

何も指定しない場合は、masterブランチのHEADが取得されてマイグレーションされていることが確認できます。

$ export POSTGRESQL_URL='postgres://postgres:password@postgres:5432/example?sslmode=disable'
$ export SOURCE_URI='github://your-name:personal-access-token@your-owner/youre-repo/migrations'
$ migrate -database ${POSTGRESQL_URL} -source ${SOURCE_URI} up
1085649617/u create_users_table (15.3183ms)
1185749658/u add_city_to_users (12.8733ms)
1285849751/u add_index_on_user_emails (10.8527ms)
1385949617/u create_books_table (11.0747ms)
1485949617/u create_movies_table (14.886ms)
1585849751/u just_a_comment (11.1459ms)
1685849751/u another_comment (13.8439ms)
1900000000/u create_sample_table (12.2614ms)

URIにDevelopブランチ指定ありの場合

URIに #develop のようにブランチを指定すると、developブランチが取得されてマイグレーションされていることが確認できます。

$ export POSTGRESQL_URL='postgres://postgres:password@postgres:5432/example?sslmode=disable'
$ export SOURCE_URI='github://your-name:personal-access-token@your-owner/youre-repo/migrations#develop'
$ migrate -database ${POSTGRESQL_URL} -source ${SOURCE_URI} up
1085649617/u create_users_table (15.8906ms)
1185749658/u add_city_to_users (10.8608ms)
1285849751/u add_index_on_user_emails (16.6914ms)
1385949617/u create_books_table (14.0107ms)
1485949617/u create_movies_table (14.2851ms)
1585849751/u just_a_comment (9.2893ms)
1685849751/u another_comment (14.0647ms)
1785849751/u another_comment (12.8647ms)
1885849751/u another_comment (13.949ms)

URIにv0.1.0のタグ指定ありの場合

URIに #v0.1.0 のようにタグを指定すると、タグのリビジョンで取得されてマイグレーションされていることが確認できます。

$ export POSTGRESQL_URL='postgres://postgres:password@postgres:5432/example?sslmode=disable'
$ export SOURCE_URI='github://your-name:personal-access-token@your-owner/youre-repo/migrations#v0.1.0'
$ migrate -database ${POSTGRESQL_URL} -source ${SOURCE_URI} up
1085649617/u create_users_table (9.799ms)
1185749658/u add_city_to_users (13.4742ms)
1285849751/u add_index_on_user_emails (16.0135ms)
1385949617/u create_books_table (8.895ms)

[応用編2] AWS S3からマイグレーションファイルを取得してみよう

1. S3にマイグレーションファイルをアップロード

S3には先ほどと同様に公式のGitHubよりSQLのexampleをアップロードします。ディレクトリ構造はGitHubと同じですので、詳細はそちらを参照してください。

2. S3のアクセス権の設定

S3にアクセスするためユーザには AmazonS3ReadOnlyAccess ポリシーを付与します。(検証目的以外で利用される場合には、アクセス範囲を絞ることをおすすめいたします)

3. マイグレーションを実行

執筆時点では、S3にアクセスするためのCredentialsの設定方法の記載がありません3が、AWS go sdkを利用しているため以下のように環境変数4に設定します。今回はS3バケットのみ東京リージョンにあるため、リージョンには ap-northeast-1 を指定しています。

$ export AWS_ACCESS_KEY_ID=YOUR_AKID
$ export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
$ export AWS_REGION=ap-northeast-1

マイグレーションファイルが置いてあるS3を指定して実行します。

$ export POSTGRESQL_URL='postgres://postgres:password@postgres:5432/example?sslmode=disable'
$ export SOURCE_URI='s3:///migrations'
$ migrate -database ${POSTGRESQL_URL} -source ${SOURCE_URI} up
1085649617/u create_users_table (14.0349ms)
1185749658/u add_city_to_users (13.6978ms)
1285849751/u add_index_on_user_emails (16.2591ms)
1385949617/u create_books_table (17.9426ms)
1485949617/u create_movies_table (14.4656ms)
1585849751/u just_a_comment (8.4976ms)
1685849751/u another_comment (15.1989ms)
1900000000/u create_sample_table (14.7474ms)

[Note]

  • GitHubを利用した場合のURIのフォーマットは aws_s3をご覧ください

[参考] Amazon Aurora (PostgreSQL互換) にマイグレーションしてみよう

以下のようなAmazon Aurora を作成してマイグレーションを行ってみました。

  • リージョン: バージニア北部(us-east-1)
  • インスタンスサイズ: t3.db.medium
  • バージョン: Aurora PostgreSQL (Compatible with PostgreSQL 11.6)

今回は先述のGitHubのリポジトリから develop ブランチを指定し、Auroraにマイグレーションを試してみます。

$ export POSTGRESQL_URL='postgres://postgres:password@database.cluster-xxxxxxxx.us-east-1.rds.amazonaws.com:5432/example?sslmode=disable'
$ export SOURCE_URI='github://your-name:personal-access-token@your-owner/youre-repo/migrations#develop'
$ migrate -database ${POSTGRESQL_URL} -source ${SOURCE_URI} up
1085649617/u create_users_table (2.0493698s)
1185749658/u add_city_to_users (3.8259861s)
1285849751/u add_index_on_user_emails (5.6180448s)
1385949617/u create_books_table (7.4065352s)
1485949617/u create_movies_table (9.2197275s)
1585849751/u just_a_comment (10.9430546s)
1685849751/u another_comment (12.7785849s)
1785849751/u another_comment (14.5836142s)
1885849751/u another_comment (16.3808016s)

今回試したようなSQLでは問題なく動作しておりますが、作者公式サイトではAmazon Aurora (PostgreSQL互換)に対応していると明記しておりません。また使用する際は利用者様の責任においてご利用ください。

参考


  1. https://hub.docker.com/r/migrate/migrate 
  2. https://www.postgresql.org/download/linux/ubuntu/ 
  3. https://github.com/golang-migrate/migrate/tree/master/source/aws_s3 
  4. https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials