Flyway でスキーマ migration を段階的に育てていく運用方法
はじめに
「Flywayを入れたけど、どう使えばいいのかわからない」
最初にFlywayを触ったとき、そんな状態でした。
公式ドキュメントを読むと V1__create_table.sql から始まる例が載っていますが、
- 既存のDB(dvdrental)があるとき、どう始めるか
- 開発中にスキーマを追加・修正するとき、どの単位でファイルを作るか
- 本番とローカルで状態が合わなくなったとき、どう対処するか
このような実運用上の疑問が解決しにくかったです。
この記事は、実際のプロジェクトでFlywayをどう使っているかの記録です。
Flywayとは
DBスキーマの変更履歴をSQLファイルで管理するツールです。
resources/db/migration/
V1__initial_schema.sql
V2__add_customer_app_schema.sql
V3__add_taste_tags_to_film.sql
- アプリ起動時に未適用のSQLを順番に実行する
- 適用済みのSQLは
flyway_schema_historyテーブルに記録される - 一度適用したSQLは変更不可(変更するなら新しいSQLを追加)
セットアップ
<!-- pom.xml -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
# application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # 既存DBに適用する場合に必要
baseline-version: 0
既存DBへの適用(dvdrental のケース)
dvdrental は既存のサンプルDBです。テーブルが既に存在する状態でFlywayを追加しました。
baseline-on-migrate の重要性
spring:
flyway:
baseline-on-migrate: true
baseline-version: 0
既存DBに初めてFlywayを適用するとき、このオプションがないと
「テーブルが既に存在するのにV1のSQLを実行しようとして失敗する」問題が起きます。
baseline-on-migrate: true を設定すると、Flywayは「V0以前の状態はこのDBが初期状態」として扱い、
V1以降のSQLだけを適用します。
初期SQLの書き方
既存テーブルはFlywayの管理外なので、V1は「今ある状態からの差分」だけを書けばよいです。
-- V1__baseline.sql(空ファイルまたはコメントのみ)
-- dvdrental の既存テーブルはFlywayの管理外
-- V2以降でcustomer_app スキーマへの追加を管理する
実際に使ったmigrationファイル
V2:customer_app スキーマの作成
-- V2__create_customer_app_schema.sql
CREATE SCHEMA IF NOT EXISTS customer_app;
CREATE TABLE customer_app.app_user (
user_id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
full_name VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
V3:映画タグカラムの追加
-- V3__add_taste_tags_to_film.sql
ALTER TABLE film ADD COLUMN IF NOT EXISTS taste_tags TEXT[];
COMMENT ON COLUMN film.taste_tags IS 'LLMで自動生成した雰囲気タグ';
V4:カートテーブルの作成
-- V4__create_cart_tables.sql
CREATE TABLE customer_app.cart (
cart_id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES customer_app.app_user(user_id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE customer_app.cart_item (
cart_item_id BIGSERIAL PRIMARY KEY,
cart_id BIGINT NOT NULL REFERENCES customer_app.cart(cart_id),
film_id INTEGER NOT NULL REFERENCES film(film_id),
added_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
ファイル命名の規則
V{バージョン}__{説明}.sql
- バージョンは整数またはドット区切り(
V1,V1.1,V2など) - アンダースコアが2つ(
__) - 説明はスネークケース
V1__baseline.sql
V2__create_customer_app_schema.sql
V3__add_taste_tags_to_film.sql
V3_1__add_index_to_film_taste_tags.sql ← V3の後に追加したい場合
よくある失敗と対処法
① 適用済みSQLを誤修正してしまった
ERROR: Detected failed migration to version 3
FlywayException: Validate failed:
Detected applied migration not resolved locally: 3
Flywayは適用済みSQLのチェックサムを記録しています。
変更すると不一致エラーが出ます。
対処法: 絶対に変更しない。修正が必要なら新しいバージョンのSQLを作る。
② ローカルと本番でFlywayの状態がずれた
開発中にV3を適用してから、V2のSQLを直したくなることがあります。
その場合は flyway_schema_history テーブルで状態を確認します。
SELECT * FROM flyway_schema_history ORDER BY installed_rank;
③ テストでFlywayを使いたくない
# application-test.yml
spring:
flyway:
enabled: false
jpa:
hibernate:
ddl-auto: create-drop # テスト用にHibernateでスキーマを管理
開発フロー
1. 新機能のDBスキーマが必要になる
↓
2. V{次の番号}__{説明}.sql を作成
↓
3. ローカルでアプリ起動 → Flywayが自動適用
↓
4. 動作確認
↓
5. コミット → 本番デプロイ時にも自動適用
「スキーマの変更 = SQLファイルの追加」という運用になるので、
DBの変更がGitの差分として残り、レビューやロールバックがしやすくなります。
まとめ
| ポイント | 詳細 |
|---|---|
| 既存DBへの適用 | baseline-on-migrate: true で初期状態をセット |
| ファイル命名 | V{バージョン}__{説明}.sql(Vは大文字、__は2つ) |
| 一度適用したSQLは変更禁止 | 修正は新しいバージョンで |
| 追加の単位 | テーブル作成・カラム追加・インデックス追加など操作ごとにファイルを分ける |
Flywayを使うとDBのスキーマ変更が「コードの変更」と同じように扱えるようになります。
「本番のDBがどうなっているか」を確認するためにDBに直接ログインする頻度が大幅に減ります。
このシリーズの記事マップ
→ dvdrental 管理アプリと対になるエンドユーザー向けDVDレンタルアプリを作っている話 — Vue 3 + Spring Boot の全体構成と記事マップ