mcpField
F
flyway-migrations

flyway-migrations

github.com/rrezartprebrezaデータ・データベース328132 回インストール6 日前に更新
リポジトリ

flyway-migrations について

flyway-migrationsは、Flywayのデータベースマイグレーションを作成・管理する開発スキルで、スキーマ変更を扱う開発者向けです。安全な変更方法、命名と順序のルール、既存データベースの導入、デプロイ時の注意点を扱います。

主な機能

  1. 01バージョン付き・リピータブルな命名
  2. 02安全なスキーマ変更パターン
  3. 03既存データベースのベースライン手順
  4. 04並行マイグレーションの調整
  5. 05デプロイ前の検証チェック

活用シーン

  1. 01適用済みマイグレーションを変更せずにスキーマを追加
  2. 02既存データベースをFlyway管理下に置く
  3. 03マイグレーションのバージョン競合を解消
  4. 04CI/CD向けのマイグレーション検証を準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rrezartprebreza/spring-boot-skills --skill flyway-migrations

インストール後、Claude Code で/flyway-migrationsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
examples/
bad-migration.sql
good-migration-concurrent-index.sql
good-migration.sql
templates/
DevDataSeeder.java
V1__create_table_template.sql

詳細

Flyway のマイグレーション

ファイルの命名規則

src/main/resources/db/migration/

V{version}__{description}.sql       ← versioned (run once)
R__{description}.sql                ← repeatable (run when checksum changes)
U{version}__{description}.sql       ← undo (requires Flyway Teams)

Examples:
V1__create_users_table.sql
V2__create_orders_table.sql
V2.1__add_order_status_index.sql
V3__add_customer_email_to_orders.sql
R__create_reporting_views.sql

ルール:

  • バージョンと説明の間には二重アンダースコア __ を使用します
  • 説明内のスペースにはアンダースコア _ を使用します
  • バージョンは連番にします。欠番を後から埋めてはいけません
  • いずれかの環境で実行済みのマイグレーションは、決して変更してはいけません

マイグレーションの例

-- V1__create_users_table.sql
CREATE TABLE users (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email       VARCHAR(255) NOT NULL UNIQUE,
    password    VARCHAR(255) NOT NULL,
    role        VARCHAR(50)  NOT NULL DEFAULT 'USER',
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);

-- V2__create_orders_table.sql
CREATE TABLE orders (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id         UUID NOT NULL REFERENCES users(id),
    status          VARCHAR(50) NOT NULL DEFAULT 'PENDING',
    total_amount    NUMERIC(12, 2) NOT NULL DEFAULT 0,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orders_user_id  ON orders(user_id);
CREATE INDEX idx_orders_status   ON orders(status);
CREATE INDEX idx_orders_created  ON orders(created_at DESC);

-- V3__add_shipping_address_to_orders.sql
-- Adding a column — always nullable or with default (safe for existing rows)
ALTER TABLE orders
    ADD COLUMN shipping_address TEXT,
    ADD COLUMN shipped_at TIMESTAMPTZ;

安全なマイグレーションのパターン

-- ✅ Safe: add nullable column
ALTER TABLE orders ADD COLUMN notes TEXT;

-- ✅ Safe: add column with default
ALTER TABLE orders ADD COLUMN priority INT NOT NULL DEFAULT 0;

-- ✅ Safe: rename via expand → migrate → contract, spread across deploys
-- Step 1 (V5): add new column (nullable — old instances keep inserting without it)
ALTER TABLE orders ADD COLUMN customer_email VARCHAR(255);
-- Step 2 (V5): backfill
UPDATE orders SET customer_email = (SELECT email FROM users WHERE users.id = orders.user_id);
-- Step 3 (V7, only after EVERY writer populates customer_email): add the check as NOT VALID —
-- instant, no table scan, but new rows are already enforced
ALTER TABLE orders ADD CONSTRAINT orders_customer_email_nn CHECK (customer_email IS NOT NULL) NOT VALID;
-- Step 4 (V8 — a SEPARATE migration, so the V7 lock is already released): validate without
-- blocking writes, then SET NOT NULL reuses the validated check and skips the scan (PG 12+)
ALTER TABLE orders VALIDATE CONSTRAINT orders_customer_email_nn;
ALTER TABLE orders ALTER COLUMN customer_email SET NOT NULL;
-- Step 5 (V9, after no deployed code reads user_id): drop old column
ALTER TABLE orders DROP COLUMN user_id;

-- ✅ Safe: add index CONCURRENTLY (no table lock in Postgres) — TWO rules, both required:
-- 1. Put it ALONE in its own migration file. Flyway detects CONCURRENTLY as
--    non-transactional; mixed with any other statement the migration is rejected:
--    "Detected both transactional and non-transactional statements".
-- 2. Set spring.flyway.postgresql.transactional-lock=false. Flyway's default
--    advisory lock holds an open transaction on a second connection, and
--    CONCURRENTLY waits for every open transaction — including Flyway's own.
--    The migration hangs forever. Session-level locks still block concurrent migrators.
-- V6__index_orders_email.sql (after V5 adds the column)
CREATE INDEX CONCURRENTLY idx_orders_email ON orders(customer_email);

-- ❌ Dangerous: rename column directly (breaks running app)
ALTER TABLE orders RENAME COLUMN user_id TO customer_id;

-- ❌ Dangerous: NOT NULL without default on large table (locks table)
ALTER TABLE orders ADD COLUMN priority INT NOT NULL; -- will fail on existing rows

application.yml

spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: false  # reject non-empty schemas without migration history
    validate-on-migrate: true
    out-of-order: false         # enforce sequential execution
    postgresql:
      transactional-lock: false # required for CREATE INDEX CONCURRENTLY (see above)

既存のデータベースをオンボーディングする

通常のアプリケーション設定では、自動ベースライン設定を無効にしておきます。すでにテーブルが存在するものの、Flyway の履歴がないデータベースの場合:

  1. データベースをバックアップし、接続先 URL、データベース、スキーマ、認証情報が正しいことを確認します。
  2. スキーマとマイグレーションを比較し、すでに適用されているバージョンを明示的に選択します。
  3. その接続先に対して、一度限りの flyway baseline -baselineVersion=<verified-version> を実行します。
  4. flyway info を確認してから、後続のバージョンのみをマイグレーションします。起動エラーを回避するためだけに、状態不明または一部のみマイグレーションされたスキーマをベースラインにしてはいけません。

これは、新しい環境向けのベースラインマイグレーションスクリプトとは異なります。自動ベースライン設定を有効にすると、Flyway の誤ったデータベースに対する安全チェックが無効になります: baseline-on-migrate の公式ドキュメント。

シードデータ(test/dev 専用)

// Use Spring profiles, not Flyway, for seed data
@Component
@Profile("dev")
@RequiredArgsConstructor
public class DevDataSeeder implements ApplicationRunner {
    private final UserRepository userRepository;

    @Override
    public void run(ApplicationArguments args) {
        if (userRepository.count() == 0) {
            userRepository.save(User.createAdmin("[email protected]", "password123"));
        }
    }
}

チームのワークフロー:並行して行うマイグレーション

  • 複数の開発者が同時にマイグレーションを作成すると、バージョンの競合が発生します
  • 解決策:共有のトラッカー(Slack チャンネル、Wiki ページ)またはタイムスタンプベースのバージョン(V20260414_1__)を使用します
  • 2 つのマイグレーションが同じバージョンを対象とする場合、どちらかの開発者が自身のバージョンを繰り上げる必要があります
  • コミット前に flyway info を実行し、バージョンの欠番や重複がないか確認します
  • CI/CD では、競合を早期に検出するため、デプロイ前の手順として flyway validate を実行します
  • 本番環境では、決して out-of-order: true を設定してはいけません。マイグレーション順序のバグが隠れてしまいます

注意点

  • エージェントが flyway-core のみを追加する — Flyway 10(Boot 3.3 以降)ではデータベースサポートが flyway-database-<db> モジュール(例:flyway-database-postgresql)に含まれます。これがないと起動時に "Unsupported Database" が発生します
  • エージェントがファイル名を V1_create_users.sql(アンダースコア 1 つ)にする — 二重の __ が必要です
  • エージェントが既存のマイグレーションファイルを変更する — 実行済みのマイグレーションは決して編集してはいけません
  • エージェントがデフォルト値なしで NOT NULL 列を追加する — NULL 許容にするか、デフォルト値を指定します
  • エージェントが列を直接リネームする — デプロイをまたいで、列の追加、データのバックフィル、削除を複数段階で行います
  • エージェントが Flyway のマイグレーションでデータをシードする — 代わりに @Profile("dev") のシーダーを使用します
  • エージェントが 1 つのマイグレーション内で CREATE INDEX CONCURRENTLY と他のステートメントを混在させる — Flyway は「トランザクション対象のステートメントとトランザクション対象外のステートメントが両方ある」として拒否します。専用のファイルに分けてください
  • エージェントが spring.flyway.postgresql.transactional-lock=false を指定せずに CONCURRENTLY のマイグレーションをリリースする — インデックスの構築が Flyway 自身のアドバイザリロックトランザクションの終了待ちとなり、デプロイが停止します
  • エージェントがインデックスを省略する — 外部キーと、WHERE/ORDER BY で使用する列には必ずインデックスを設定します
  • エージェントが DROP TABLE または DROP COLUMN を最初の手順とするマイグレーションを作成する — まず新しい列を追加してコードをデプロイし、その後のマイグレーションで古い列を削除します

コメント