- Published on
スキーママイグレーションツール比較 — Flyway、Liquibase、golang-migrate、Alembic、Atlas
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 中心となる概念
- Flyway — シンプルな SQL ファースト
- Liquibase — changelog 中心のマルチフォーマット
- golang-migrate — 軽量で明示的な up/down
- Alembic — SQLAlchemy のための ORM マイグレーション
- Atlas — 宣言的スキーマと自動 diff
- ツール比較表
- CI 連携
- 衝突の解決
- 選択ガイド
- よくある落とし穴
- おわりに
- 参考資料
はじめに
アプリケーションのコードは Git でバージョン管理する一方で、データベースのスキーマは誰かが手作業で ALTER TABLE を実行していた時代がありました。その結果、本番 DB とステージング DB のスキーマが微妙にずれ、新しく加わったメンバーのローカル環境はさらに別の状態になっていました。スキーママイグレーションツールは、まさにこの問題を解決するために登場しました。
スキーママイグレーションツールの核となる約束はシンプルです。「スキーマ変更をコードのようにバージョン管理し、どの環境でも同じ順序で再現可能に適用する」というものです。本記事では、最も広く使われている五つのツール、Flyway、Liquibase、golang-migrate、Alembic、Atlas を取り上げます。各ツールがどのモデルを採用しているか、バージョン管理とチェックサムをどう扱うか、ロールバックと CI 連携をどこまで支援するかを比較します。
ツールを選ぶ前に知っておくとよいことがあります。マイグレーションツールには大きく二つの哲学があります。一つは「変更単位」を積み重ねていく**バージョンベース(versioned)方式で、もう一つは「最終状態」を宣言するとツールが変更経路を計算する宣言的(declarative)**方式です。この違いを理解することが、ツール選択の出発点になります。
中心となる概念
バージョンベースのマイグレーション
バージョンベースのマイグレーションは最も直感的なモデルです。スキーマを変えたいたびに新しいマイグレーションファイルを書き、ツールはこれらのファイルを順番に一度だけ適用します。適用済みかどうかは専用の追跡テーブルに記録されます。
たとえば Flyway はスキーマ履歴テーブルを、golang-migrate は schema_migrations テーブルを作り、どのバージョンまで適用されたかを記録します。次にツールを実行すると、まだ適用されていないマイグレーションだけを選んで実行します。
[V1] テーブル作成 -> 適用済み(history に記録)
[V2] カラム追加 -> 適用済み(history に記録)
[V3] インデックス追加 -> 未適用 <- 次回実行時にこれだけ適用
このモデルの長所は、変更履歴がそのまま残ることです。誰がいつどの変更をしたか、マイグレーションファイル自体が証拠になります。短所は、ファイルが累積し続けること、そして二人の開発者が同時に同じバージョン番号でマイグレーションを作ると衝突することです。
宣言的マイグレーション
宣言的マイグレーションはアプローチが異なります。開発者は「スキーマが最終的にこうあってほしい」という目標状態だけを宣言します。ツールが現在の DB 状態と目標状態を比較(diff)し、その間を埋めるマイグレーションを自動生成します。Atlas と Prisma の一部のワークフローがこの方式を採用しています。
現在のスキーマ(DB) + 目標スキーマ(宣言ファイル)
| |
+--------- diff --------+
|
自動生成された変更計画
宣言的方式の魅力は、「今何が変わるべきか」を人が直接計算しなくてよいことです。ただし自動生成された変更が常に安全とは限らないため、生成されたマイグレーションを人が確認する工程が必ず必要です。
チェックサムと整合性
ほとんどのツールは、すでに適用されたマイグレーションファイルの内容をチェックサム(通常 CRC32 またはハッシュ)で記録します。次に実行するときにファイル内容が変わっていればチェックサムが異なるため、エラーを出します。これは「すでに適用されたマイグレーションを密かに修正してはならない」という不変規則を強制する仕組みです。適用済みのマイグレーションは修正せず、新しいマイグレーションを追加するのが原則です。
Flyway — シンプルな SQL ファースト
Flyway は「SQL こそが真実」という哲学を持つツールです。ほとんどのマイグレーションを普通の SQL ファイルとして書き、ファイル名の規則がそのままバージョンになります。
ファイル名の規則
Flyway の versioned マイグレーションは V で始まる厳格な命名規則に従います。
V1__create_users_table.sql
V2__add_email_to_users.sql
V2.1__add_index_on_email.sql
R__refresh_reporting_view.sql
V は versioned、R は repeatable(再実行可能、チェックサムが変わるたびに再実行)マイグレーションを意味します。バージョン番号の後にアンダースコア二つ、その後に説明が続きます。
実際の例
-- V1__create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- V2__add_status_to_users.sql
ALTER TABLE users
ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
適用はコマンド一行で済みます。
flyway -url=jdbc:postgresql://localhost:5432/app \
-user=app -password=secret migrate
Flyway は flyway_schema_history テーブルに各バージョンのチェックサム、適用時刻、実行時間を記録します。Community エディションは無料ですが、自動ロールバック(undo)機能は有料の Teams / Enterprise エディションでのみ提供されます。無料版では、ロールバック用のマイグレーションを自分で書いて前方に適用する方式で対応します。
ベースライン
すでにデータが入っている既存 DB に Flyway を導入する場合は、baseline コマンドで開始点を定めます。こうすると、それ以前のスキーマはすでに存在するとみなし、以降のマイグレーションだけを適用します。
flyway -baselineVersion=1 baseline
Liquibase — changelog 中心のマルチフォーマット
Liquibase は SQL を直接書く代わりに、changelog という抽象的な変更単位でスキーマを記述します。changelog は XML、YAML、JSON、または SQL 形式で書けます。各変更単位は changeSet と呼ばれ、一意な id と author を持ちます。
XML changelog の例
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<changeSet id="1" author="youngju">
<createTable tableName="users">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="email" type="VARCHAR(255)">
<constraints nullable="false" unique="true"/>
</column>
</createTable>
</changeSet>
<changeSet id="2" author="youngju">
<addColumn tableName="users">
<column name="status" type="VARCHAR(20)" defaultValue="active"/>
</addColumn>
</changeSet>
</databaseChangeLog>
YAML changelog の例
同じ変更を YAML で書くと次のようになります。
databaseChangeLog:
- changeSet:
id: 1
author: youngju
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
nullable: false
抽象的な changelog の最大の長所はデータベース非依存性です。createTable 一行が、PostgreSQL では BIGSERIAL に、MySQL では AUTO_INCREMENT に適切に変換されます。複数種類の DB を同時に支援する必要がある製品なら、この点が大きな魅力です。
ロールバック
Liquibase はロールバックを第一級の市民として扱います。createTable のような変更は逆操作を自動的に推論し、複雑な変更では rollback ブロックを直接明示できます。
liquibase --changeLogFile=changelog.xml rollbackCount 1
Liquibase は DATABASECHANGELOG テーブルに適用履歴を、DATABASECHANGELOGLOCK テーブルで同時実行を防ぐロックを管理します。
golang-migrate — 軽量で明示的な up/down
golang-migrate は Go エコシステムで広く使われる軽量ツールです。哲学は明快です。すべてのマイグレーションは up と down の二つの SQL ファイルで構成され、ツールはその SQL をそのまま実行するだけです。
ファイル構成
migrations/
000001_create_users.up.sql
000001_create_users.down.sql
000002_add_status.up.sql
000002_add_status.down.sql
-- 000001_create_users.up.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE
);
-- 000001_create_users.down.sql
DROP TABLE users;
実行
migrate -database "postgres://app:secret@localhost:5432/app?sslmode=disable" \
-path ./migrations up
migrate -database "postgres://app:secret@localhost:5432/app?sslmode=disable" \
-path ./migrations down 1
golang-migrate は schema_migrations テーブルに現在のバージョンと dirty フラグを記録します。マイグレーション途中で失敗すると dirty 状態のまま残り、ユーザーが force コマンドで状態を整理してから再試行する必要があります。ロールバックは down ファイルを書いた分だけ可能なので、down SQL を丁寧に書く習慣が重要です。自動チェックサム検証は標準では提供されないため、適用済みのマイグレーションを修正しない規律はチーム自身で守る必要があります。
Alembic — SQLAlchemy のための ORM マイグレーション
Alembic は Python の SQLAlchemy ORM と対になるマイグレーションツールです。マイグレーションを Python コードとして書き、最も強力な機能は ORM モデルと実際の DB を比較してマイグレーションを自動生成する autogenerate です。
revision ファイル
各マイグレーションは revision 識別子と down_revision で連結されたチェーンを成します。このチェーンが適用順序を決めます。
"""add status to users
Revision ID: a1b2c3d4
Revises: 9f8e7d6c
"""
from alembic import op
import sqlalchemy as sa
revision = "a1b2c3d4"
down_revision = "9f8e7d6c"
def upgrade():
op.add_column(
"users",
sa.Column("status", sa.String(20), nullable=False, server_default="active"),
)
def downgrade():
op.drop_column("users", "status")
autogenerate
モデルを修正したあとに次のコマンドを実行すると、Alembic が差分を検知してマイグレーションの下書きを作ってくれます。
alembic revision --autogenerate -m "add status to users"
alembic upgrade head
alembic downgrade -1
autogenerate は便利ですが完璧ではありません。カラム型の変更や一部の制約など、検知できない変更があるため、生成されたマイグレーションは必ず人が確認する必要があります。Alembic は alembic_version テーブルに現在の revision を記録します。
Prisma Migrate との比較
Node エコシステムの Prisma Migrate も ORM ベースのマイグレーションの好例です。schema.prisma ファイルがモデルの単一の信頼できる情報源となり、prisma migrate dev コマンドが SQL マイグレーションを生成します。Alembic が Python コードで変更を表現するのに対し、Prisma は宣言的なスキーマファイルと生成された SQL を併せて管理する点が異なります。
Atlas — 宣言的スキーマと自動 diff
Atlas は比較的新しいツールで、宣言的マイグレーションを前面に押し出しています。開発者は HCL(または SQL、ORM 定義)で望む最終スキーマを宣言し、Atlas が現在の状態と比較してマイグレーションを生成します。
HCL によるスキーマ宣言
schema "public" {}
table "users" {
schema = schema.public
column "id" {
type = bigint
null = false
}
column "email" {
type = varchar(255)
null = false
}
primary_key {
columns = [column.id]
}
index "idx_users_email" {
unique = true
columns = [column.email]
}
}
diff によるマイグレーション生成
スキーマファイルを修正したあとに migrate diff を実行すると、Atlas が既存のマイグレーションディレクトリと目標スキーマの差を計算し、新しいマイグレーションファイルを作ってくれます。
atlas migrate diff add_status \
--dir "file://migrations" \
--to "file://schema.hcl" \
--dev-url "docker://postgres/16/dev"
atlas migrate apply \
--dir "file://migrations" \
--url "postgres://app:secret@localhost:5432/app?sslmode=disable"
Atlas は dev-url で指定した使い捨て DB にスキーマを実際に適用してみることで、正確な diff を計算します。また versioned ワークフローと宣言的ワークフローの両方を支援するため、チームの成熟度に合わせて選べます。atlas migrate lint のような静的解析で危険な変更(たとえばカラム削除、不可逆な変更)を事前に捉える機能も強みです。
ツール比較表
五つのツールの特性を一目で整理すると次のとおりです。セルは単純なテキストのみで表記します。
| 項目 | Flyway | Liquibase | golang-migrate | Alembic | Atlas |
|---|---|---|---|---|---|
| マイグレーションモデル | SQL files | XML/YAML changelog | up/down SQL | Python code | declarative HCL |
| 主なエコシステム | JVM | JVM | Go | Python | Go, multi |
| 自動 diff 生成 | No | Partial | No | Yes (autogenerate) | Yes |
| ロールバック支援 | Paid only | Yes | Manual down | Yes | Yes |
| チェックサム検証 | Yes | Yes | No | No | Yes |
| マルチ DB 抽象化 | Partial | Yes | Partial | Yes (via ORM) | Yes |
| ベースライン | Yes | Yes | Partial | Partial | Yes |
| CI 親和性 | High | High | High | High | High |
| 宣言的モード | No | No | No | No | Yes |
上の表で「Partial」は部分的な支援、または回避策が必要であることを意味します。
CI 連携
どのツールを使っても、マイグレーションは CI パイプラインで自動的に適用するか、少なくとも検証すべきです。一般的なパターンは次のとおりです。
[コードプッシュ] -> [CI ビルド] -> [使い捨て DB 起動]
-> [マイグレーション適用] -> [テスト実行]
-> [合格時にデプロイ] -> [本番 DB にマイグレーション適用]
本番環境では、アプリケーションのデプロイとマイグレーション適用の順序を慎重に決める必要があります。カラム追加のような後方互換のある変更は先に適用しても安全ですが、カラム削除のような互換を壊す変更は、新旧バージョンが共存する無停止デプロイで段階的に進める必要があります。
無停止デプロイのための拡張・縮小パターン
拡張・縮小(expand-contract)パターンは、互換性を壊さずにスキーマを変える標準的な手法です。
ステップ1(拡張): 新しいカラム/テーブルを追加する(旧版も新版も動作)
ステップ2(二重書き込み): 新版が新しい構造にもデータを書く
ステップ3(バックフィル): 過去データを新しい構造に詰める
ステップ4(切り替え): すべての読み取りを新しい構造に移す
ステップ5(縮小): もう使われない古いカラムを削除する
衝突の解決
バージョンベースのツールで最も多い運用事故はバージョン衝突です。二人の開発者がそれぞれのブランチで同じ次のバージョン番号のマイグレーションを作ると、マージ時に同じバージョンが二つになります。
解決策はツールごとに少しずつ異なります。Flyway は同じバージョン番号を許さないため、マージ時に片方の番号を上げる必要があります。Alembic は線形バージョンの代わりに revision グラフを使うため分岐が生じることがあり、そのとき alembic merge コマンドで二つの分岐を一つに合わせるマージ revision を作ります。Atlas は宣言的スキーマが単一の信頼できる情報源なので、衝突が SQL マイグレーションではなくスキーマファイルのレベルで起き、Git マージで解決しやすいです。
最も実用的な予防策はタイムスタンプベースのファイル名を使うことです。バージョン番号の代わりに生成時刻(たとえば 20260616T101500)を使えば、二人の開発者が同時に作っても番号が重なりません。golang-migrate と Alembic はこの方式を標準またはオプションで支援します。
選択ガイド
状況別に次のように整理できます。
JVM ベースのプロジェクトで SQL を直接制御したいなら、Flyway が最もシンプルな選択です。ファイル名の規則さえ覚えればすぐ使え、参入障壁が低いです。
複数種類の DB を同時に支援する必要がある、またはロールバックを標準ワークフローにしたいなら、Liquibase が強いです。changelog 抽象化が DB の違いを吸収してくれます。
Go サービスで依存を最小化しつつ SQL を完全に制御したいなら、golang-migrate がよく合います。ただし down ファイルと整合性の規律はチームが自分で面倒を見る必要があります。
Python と SQLAlchemy を使うなら、事実上 Alembic が標準です。autogenerate で生産性が高いですが、生成結果の確認は必須です。Node と Prisma を使うなら Prisma Migrate が同じ位置を占めます。
宣言的ワークフローを試したい、またはマイグレーション静的解析で危険な変更を事前にふるい落としたいなら、Atlas が最も現代的な選択です。既存の versioned ツールと併せて導入するのにも適しています。
よくある落とし穴
最後に、どのツールを使っても共通して出会う落とし穴を整理します。
第一に、すでに適用されたマイグレーションを修正することです。チェックサム検証があるツールならエラーで防いでくれますが、ないツールでは環境ごとにスキーマがずれる静かな災害になります。適用済みのものは決して直さず、新しいマイグレーションを追加してください。
第二に、ロールバックを過信することです。DROP COLUMN を元に戻す down マイグレーションがカラムを作り直せても、消えたデータまでは復活させられません。不可逆な変更はロールバックではなく、事前のバックアップと段階的なデプロイで備えるべきです。
第三に、巨大なテーブルにロックがかかる変更です。一部の ALTER TABLE はテーブル全体をロックしてサービス障害を引き起こします。大きなテーブルにはオンライン DDL や段階的なマイグレーション戦略を適用すべきです。
第四に、マイグレーションをテストしないことです。マイグレーションもコードです。CI で使い捨て DB に適用してみて、可能なら本番に近いデータでリハーサルするのが安全です。
おわりに
スキーママイグレーションツールは結局、同じ目標に向かっています。スキーマ変更をコードのように扱い、どの環境でも安全に再現可能に適用することです。Flyway のシンプルさ、Liquibase のマルチ DB 抽象化、golang-migrate の明示性、Alembic の ORM 統合、Atlas の宣言的モデルは、それぞれ異なる強みを持っています。
正解はチームの言語エコシステム、DB の多様性、そして宣言的ワークフローへの好みによって変わります。重要なのはツール選択そのものよりも、マイグレーションをバージョン管理し、CI で検証し、適用済みの変更を不変として扱う規律です。その規律があれば、どのツールを選んでも十分に安全に運用できます。
参考資料
- Flyway 公式ドキュメント: https://flywaydb.org/documentation/
- Liquibase 公式ドキュメント: https://docs.liquibase.com/
- golang-migrate リポジトリ: https://github.com/golang-migrate/migrate
- Alembic 公式ドキュメント: https://alembic.sqlalchemy.org/
- Atlas 公式ドキュメント: https://atlasgo.io/
- Prisma Migrate ドキュメント: https://www.prisma.io/docs/orm/prisma-migrate
- PostgreSQL ALTER TABLE ドキュメント: https://www.postgresql.org/docs/current/sql-altertable.html
- MySQL 公式ドキュメント: https://dev.mysql.com/doc/