こんにちは、仲間!
今日は、データベースのマイグレーションとは何か、そしてそれがなぜ重要なのかについて話しましょう。今日の世界では、データベースに対する変更は慎重に行われ、特定のプロセスに従うべきだということは驚くべきことではありません。理想的には、これらのステップはCI/CDパイプラインに統合され、自動的にすべてが実行されるべきです。
アジェンダは以下の通りです:
- 問題は何か?
- どうやって解決するか?
- 簡単な例
- より複雑な例
- 推奨事項
- 結果
- 結論
問題は何か?
もしあなたのチームがデータベースのマイグレーションに対処したことがなく、なぜそれが必要なのか確信が持てない場合、整理してみましょう。基本を知っている場合は、先に進んでください。
主な課題
「計画的」で「スムーズな」データベース変更を行う際には、サービスの可用性を維持し、SLA要件を満たす必要があります(ユーザーがダウンタイムや遅延に苦しむことがないように)。500万人のユーザーを持つテーブルの列のタイプを変更したいと想像してみてください。これを「正面から」行うと(例えば、準備なしに単純にALTER TABLE
を実行する)、テーブルがかなりの長時間ロックされる可能性があり、ユーザーはサービスなしに放置されてしまいます。
そのような頭痛を避けるために、次の2つのルールに従ってください:
- テーブルをロックしない方法でマイグレーションを適用する(または少なくともロックを最小限に抑える)。
- 列の型を変更する必要がある場合、最初に正しい型の新しい列を作成し、その後古い列を削除する方が簡単なことがよくあります。
別の問題:バージョン管理とロールバック
時にはマイグレーションをロールバックする必要があります。
これを手動で行うこと—本番データベースにアクセスしてデータをいじること—は、リスクが高いだけでなく、直接アクセスできない場合はほぼ不可能です。そこで、専用のマイグレーションツールが役立ちます。これらは、変更をクリーンに適用し、必要に応じて元に戻すことを可能にします。
どうやって修正するのか?適切なツールを使用する
各言語とエコシステムには独自のマイグレーションツールがあります:
- Javaの場合、LiquibaseやFlywayが一般的です。
- Goの場合、人気の選択肢はgoose(ここで見ていくもの)です。
- などなど。
Goose:それは何で、なぜ役立つのか
Gooseは、マイグレーションを自動的に管理するための軽量なGoユーティリティです。それは次のことを提供します:
- シンプルさ。最小限の依存関係とマイグレーションのための透明なファイル構造。
- 多用途性。さまざまなDBドライバー(PostgreSQL、MySQL、SQLiteなど)をサポートします。
- 柔軟性。SQLまたはGoコードでマイグレーションを書くことができます。
Gooseのインストール
go install github.com/pressly/goose/v3/cmd/goose@latest
動作の仕組み:マイグレーション構造
デフォルトでは、Gooseはdb/migrations
内のマイグレーションファイルを探します。各マイグレーションはこの形式に従います:
NNN_migration_name.(sql|go)
NNN
はマイグレーション番号(例:001
、002
など)です。- その後、任意の説明的な名前を付けることができます。例えば
init_schema
のように。 - 拡張子は
.sql
または.go
にできます。
SQLマイグレーションの例
ファイル:001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
最初の例
カラムタイプの変更(String → Int)
ここでは、users
テーブルにage
というカラムがあり、そのタイプがVARCHAR(255)
であるとします。これをINTEGER
に変更したいと考えています。マイグレーションは次のようになります(ファイル005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
ここで何が起こっているか:
-
アップマイグレーション
age
カラムをINTEGER
に変更します。USING (age::INTEGER)
句は、PostgreSQLに既存のデータを新しい型に変換する方法を指示します。- このマイグレーションは、
age
に数値でないデータが含まれている場合に失敗します。その場合、より複雑な戦略が必要になります(下記参照)。
-
ダウンマイグレーション
- ロールバックすると、
age
をVARCHAR(255)
に戻します。 - 再び
USING (age::TEXT)
を使用して、INTEGER
からテキストに戻します。
- ロールバックすると、
第二の複雑なケース:マルチステップマイグレーション
もし age
列に雑なデータ(数字だけでなく)が含まれている可能性がある場合、いくつかのステップで行う方が安全です:
- 新しい列(
age_int
)をINTEGER
型で追加します。 - 有効なデータを新しい列にコピーし、無効なエントリを処理または削除します。
- 古い列を削除します。
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
適切なロールバックを可能にするために、Down
セクションは逆のアクションを反映します。
自動化が鍵です
時間を節約するために、マイグレーションコマンドを Makefile(または他のビルドシステム)に追加するのが非常に便利です。以下は、PostgreSQL のための主要な Goose コマンドを含む Makefile の例です。
仮定しましょう:
- データベースの DSN は
postgres://user:password@localhost:5432/dbname?sslmode=disable
です。 - 移行ファイルは
db/migrations
にあります。
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
使い方は?
1. 新しい移行(SQLファイル)を作成する。これによりdb/migrations/002_add_orders_table.sql
というファイルが生成されます。
make new-migration NAME=add_orders_table
2. すべての移行を適用する。Gooseはデータベースにschema_migrations
テーブルを作成し(存在しない場合)、昇順で新しい移行を適用します。
make migrate-up
3. 最後の移行をロールバックする。最後の移行を元に戻します。
make migrate-down
4. すべての移行をロールバックする(本番環境では注意してください)。完全なリセット。
make migrate-reset
5. 移行のステータスを確認する。
make migrate-status
出力例:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
概要
移行ツールとMakefileを使用することで、
- 本番データベースへの直接アクセスを制限し、変更を移行のみで行います。
- データベースのバージョンを簡単に追跡し、問題が発生した場合にそれらを元に戻すことができます。
- データベースの変更の一貫した履歴を維持します。
- マイクロサービスの世界で実行中の本番環境を壊さない「スムーズな」移行を行います。
- 追加の検証を取得します — すべての変更はPRとコードレビュープロセスを経ることになります(それらの設定がある場合を想定しています)。
また、これらすべてのコマンドをCI/CDパイプラインに簡単に統合することができる利点があります。そして覚えておくべきこと — 何よりもセキュリティが重要です。
例えば:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
結論とヒント
主なアイデアは非常にシンプルです:
- マイグレーションは小規模で頻繁に行いましょう。レビュー、テスト、必要に応じて元に戻すのが簡単です。
- すべての環境で同じツールを使用しましょう。これにより、開発、ステージ、プロダクションが同期します。
- マイグレーションをCI/CDに統合しましょう。そうすれば、特定の誰かに手動で実行してもらう必要がなくなります。
このようにして、データベース構造を変更するための信頼性が高く制御されたプロセスを持つことができます — プロダクションを壊さず、何か問題が発生した場合に迅速に対応できるプロセスです。
マイグレーションがうまくいくことを祈っています!
お読みいただきありがとうございます!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service