Flyway

Flywayデータベースマイグレーションツール完全ガイド

目次

  1. 導入
  2. Flywayの基本概念
  3. アーキテクチャ概要
  4. インストールと初期設定
  5. マイグレーション戦略
  6. 実践的な設定例
  7. マイグレーションバージョニング
  8. 本番環境への適用
  9. トラブルシューティング
  10. ベストプラクティス

1. 導入

1.1 Flywayとは

Flyway(フライウェイ)は、Axoniq社により開発・保守されているオープンソースのデータベースマイグレーションツールです。アプリケーションのバージョン管理と同様に、データベーススキーマのバージョン管理を自動化し、チーム開発における安全かつ予測可能なデータベース進化を実現します。

Flywayの核となる哲学は「Version Control for your Database」です。この考え方により、以下のような課題を解決します:

  • スキーマのドリフト:異なるデータベース環境間のスキーマ定義の不一致
  • マイグレーション漏れ:本番環境への適用忘れや不完全な適用
  • ロールバックの困難さ:データベース変更の巻き戻し処理
  • 開発チーム間の同期不足:マイグレーション実行順序の不確定性

1.2 主要な特徴

  • シンプルで直感的: SQLファイルを配置するだけでマイグレーション実行
  • 言語非依存: JVM、CLI、Docker、Gradle、Maven、.NET等で利用可能
  • 複数データベース対応: PostgreSQL、MySQL、Oracle、SQL Server等をサポート
  • 自動バージョン管理: マイグレーション履歴を自動トラッキング
  • トランザクション安全性: ほとんどのマイグレーション実行をトランザクション内で実行
  • CLI・API・プラグイン: 様々な統合方法をサポート

1.3 適用シーン

Flywayは以下のような環境で特に有効です:

  • マイクロサービスアーキテクチャでの各サービスのスキーマ管理
  • CI/CDパイプラインへの統合
  • 複数の開発環境(開発機、ステージング、本番)の管理
  • Dockerコンテナ化されたアプリケーション
  • DevOps文化を採用する組織

2. Flywayの基本概念

2.1 マイグレーション(Migration)

マイグレーションとは、データベーススキーマの段階的な変更を記述したファイルセットです。Flywayでは以下の2種類があります:

2.1.1 Versioned Migration(バージョン管理マイグレーション)

順序付きのスキーマ変更を表現します。命名規則は以下の通りです:

V<version>__<description>.sql

例:

V1__Create_users_table.sql
V2__Add_email_to_users.sql
V3__Create_products_table.sql
V1.1__Add_index_on_users_id.sql

バージョン番号の仕様:

  • 数値のみで構成
  • ドットで区切られた複数の数値をサポート(1.0、1.1、2.0.1等)
  • アンダースコアで説明文と分割
  • 説明文中のアンダースコアが単語の区切り

2.1.2 Undo Migration(アンドゥ・マイグレーション)

バージョン管理マイグレーションを取り消すマイグレーション。命名規則:

U<version>__<description>.sql

例:

U1__Create_users_table.sql
U2__Add_email_to_users.sql

重要: Undo機能を使用するには、エディション選択およびアンドゥ機能の有効化が必要です。本番環境ではロールバックより新しいマイグレーションでの修正推奨。

2.1.3 Repeatable Migration(繰り返し可能マイグレーション)

バージョン不要で、変更検出時に繰り返し実行されるマイグレーション。命名規則:

R__<description>.sql

例:

R__Create_roles.sql
R__Populate_lookup_tables.sql
R__Refresh_materialized_views.sql

用途:

  • ビュー定義
  • 関数定義
  • 初期データの挿入
  • 権限設定
  • マテリアライズドビューのリフレッシュ

実行タイミング:

  • ファイルが初回作成時
  • チェックサムが変更された時(コンテンツ変更検出)

2.2 Flyway Schema History テーブル

Flywayは全マイグレーション実行履歴をflyway_schema_historyテーブルに保存します。

テーブル構造:

CREATE TABLE flyway_schema_history (
    installed_rank INT NOT NULL,
    version VARCHAR(50),
    description VARCHAR(255) NOT NULL,
    type VARCHAR(20) NOT NULL,
    script VARCHAR(1000) NOT NULL,
    checksum INT,
    installed_by VARCHAR(100) NOT NULL,
    installed_on TIMESTAMP NOT NULL,
    execution_time INT NOT NULL,
    success BOOLEAN NOT NULL
);

主要カラム説明:

  • installed_rank: インストール順序(1から開始、単調増加)
  • version: マイグレーションバージョン(V マイグレーションのみ)
  • description: マイグレーション説明
  • type: マイグレーション型(BASELINE, SQL, JDBC等)
  • script: スクリプトファイル名
  • checksum: ファイル内容のチェックサム(変更検出用)
  • installed_by: 実行ユーザー名
  • installed_on: 実行日時
  • execution_time: 実行時間(ミリ秒)
  • success: 実行成功/失敗

2.3 ステートマシン

Flywayのマイグレーション状態遷移:

初期状態
  ↓
マイグレーション検出 → V1が見つかった
  ↓
V1実行
  ↓
成功 → flyway_schema_historyに記録
  ↓
マイグレーション完了状態
  ↓
(次回実行時)
  ↓
V1の検証(チェックサム確認)
  ↓
チェックサムが一致 → スキップ
チェックサムが不一致 → エラー(検証失敗)
新規V2が見つかった → V2実行

3. アーキテクチャ概要

3.1 全体構成

┌─────────────────────────────────────────┐
│         Application Layer               │
│  (Java App / CLI / Gradle / Maven)      │
└──────────────────┬──────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│         Flyway Core Engine              │
│  ┌─────────────────────────────────────┐ │
│  │ 1. Configuration                    │ │
│  ├─────────────────────────────────────┤ │
│  │ 2. Migration Scanner                │ │
│  ├─────────────────────────────────────┤ │
│  │ 3. Version Resolver                 │ │
│  ├─────────────────────────────────────┤ │
│  │ 4. State Checker                    │ │
│  ├─────────────────────────────────────┤ │
│  │ 5. Executor                         │ │
│  ├─────────────────────────────────────┤ │
│  │ 6. Validator                        │ │
│  └─────────────────────────────────────┘ │
└──────────────────┬──────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│    Database Connection Manager          │
│  (JDBC / Connection Pool)               │
└──────────────────┬──────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│         Target Database                 │
│  (PostgreSQL / MySQL / Oracle / etc)    │
└─────────────────────────────────────────┘

3.2 コンポーネント詳細

3.2.1 Configuration(設定)

Flywayの動作を制御する設定層。設定項目例:

  • locations: マイグレーションファイルの配置場所
  • baselineVersion: 初期バージョン番号
  • validateOnMigrate: マイグレーション実行前の検証有効化
  • cleanDisabled: データベースクリーニング禁止フラグ
  • sqlMigrationPrefix: Vマイグレーションのプリフィックス
  • repeatableSqlMigrationPrefix: Rマイグレーションのプリフィックス

3.2.2 Migration Scanner(スキャナー)

マイグレーションファイルを発見するコンポーネント。

処理流程:

  1. locationsで指定されたディレクトリをスキャン
  2. 命名規則に合致するファイルを収集
  3. ファイルシステムおよびクラスパスから読み込み
  4. 発見されたマイグレーションをリスト化

スキャン対象:

  • ファイルシステム上のSQL/JAVAファイル
  • クラスパス上のリソース(JAR内含む)
  • ネットワークパス(設定可能)

3.2.3 Version Resolver(バージョン解決)

発見されたマイグレーションをバージョン順にソート・解決。

処理:

  1. バージョン番号を数値として解析
  2. Versionedマイグレーションを版順にソート
  3. バージョン重複チェック(重複あればエラー)
  4. Repeatableマイグレーションを整理

3.2.4 State Checker(状態検査)

データベース現在状態の確認。

確認内容:

  • スキーマ履歴テーブルの存在確認
  • 現在のマイグレーション状態確認
  • 実行済みマイグレーションの一覧取得
  • 検証失敗有無の確認

3.2.5 Executor(実行エンジン)

実際のマイグレーション実行。

実行プロセス:

  1. トランザクション開始
  2. マイグレーションスクリプト実行
  3. 実行ログをスキーマ履歴テーブルに記録
  4. トランザクションコミット

トランザクション管理:

  • ほとんどのマイグレーション実行をトランザクション内で実施
  • 実行失敗時は自動ロールバック
  • DDL(CREATE/ALTER等)がトランザクション非対応の場合は個別対応

3.2.6 Validator(検証エンジン)

マイグレーション整合性の検証。

検証項目:

  • チェックサムの一致確認
  • マイグレーションスクリプト構文チェック
  • 実行順序の妥当性確認
  • 実行済みマイグレーション未変更の確認

3.3 処理フロー

┌─────────────────────────────────┐
│   Flyway実行開始                 │
│   (migrate/validate/info等)      │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│   Database接続確立              │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│   スキーマ履歴テーブル確認      │
│   未作成 → 作成                 │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│   マイグレーション検出          │
│   locations配下をスキャン        │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│   検出マイグレーション          │
│   バージョン順にソート          │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│   未実行マイグレーション特定    │
│   履歴テーブルと比較            │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│  ┌────────────────────────────┐ │
│  │ マイグレーション検証       │ │
│  │ (validateOnMigrate有効時)  │ │
│  └────────────┬───────────────┘ │
│               │                  │
│         ┌─────▼─────┐           │
│         │ 検証成功   │           │
│         └─────┬─────┘           │
└──────────────┬──────────────────┘
               │
               ↓
┌─────────────────────────────────┐
│  ┌────────────────────────────┐ │
│  │ マイグレーション実行       │ │
│  │ (未実行 → 実行)            │ │
│  └────────────┬───────────────┘ │
│               │                  │
│         ┌─────▼──────┐          │
│         │ 実行成功    │          │
│         └─────┬──────┘          │
│               │                  │
│         ┌─────▼──────────────┐  │
│         │ 履歴テーブルに記録 │  │
│         └─────┬──────────────┘  │
└──────────────┬──────────────────┘
               │
               ↓
┌─────────────────────────────────┐
│   全マイグレーション完了        │
│   (必要に応じて繰り返し実行)    │
└────────────┬────────────────────┘
             │
             ↓
┌─────────────────────────────────┐
│   実行結果出力                  │
└─────────────────────────────────┘

4. インストールと初期設定

4.1 インストール方法

4.1.1 Javaアプリケーションへの組み込み

Maven統合

pom.xmlに以下を追加:

<dependencies>
    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
        <version>9.22.3</version>
    </dependency>
    <!-- データベースドライバ -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.6.0</version>
    </dependency>
</dependencies>

<plugins>
    <plugin>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-maven-plugin</artifactId>
        <version>9.22.3</version>
        <configuration>
            <url>jdbc:postgresql://localhost:5432/myapp</url>
            <user>postgres</user>
            <password>password</password>
        </configuration>
    </plugin>
</plugins>

Gradle統合

build.gradleに以下を追加:

plugins {
    id 'org.flywaydb.flyway' version '9.22.3'
}

dependencies {
    implementation 'org.flywaydb:flyway-core:9.22.3'
    runtimeOnly 'org.postgresql:postgresql:42.6.0'
}

flyway {
    url = 'jdbc:postgresql://localhost:5432/myapp'
    user = 'postgres'
    password = 'password'
    locations = ['filesystem:src/main/resources/db/migration']
}

4.1.2 コマンドラインツール(CLI)

ダウンロード・インストール

# macOS/Linux
wget https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/9.22.3/flyway-commandline-9.22.3-linux-x64.tar.gz
tar xzf flyway-commandline-9.22.3-linux-x64.tar.gz
cd flyway-9.22.3
./flyway -version

# またはHomebrew(macOS)
brew install flyway
flyway -version

基本コマンド

# マイグレーション実行
flyway -url=jdbc:postgresql://localhost:5432/myapp \
       -user=postgres \
       -password=password \
       -locations=filesystem:db/migration \
       migrate

# 現在状態確認
flyway -url=jdbc:postgresql://localhost:5432/myapp \
       -user=postgres \
       -password=password \
       info

# 検証実行
flyway -url=jdbc:postgresql://localhost:5432/myapp \
       -user=postgres \
       -password=password \
       validate

4.1.3 Docker

Flywayの公式Dockerイメージ利用:

FROM flyway/flyway:9.22.3

COPY db/migration /flyway/sql

CMD ["flyway", "migrate"]

実行方法:

docker run --rm \
  -v $(pwd)/db/migration:/flyway/sql \
  -e FLYWAY_URL=jdbc:postgresql://postgres:5432/myapp \
  -e FLYWAY_USER=postgres \
  -e FLYWAY_PASSWORD=password \
  flyway/flyway:9.22.3 migrate

4.2 初期設定

4.2.1 マイグレーションディレクトリ構造

推奨ディレクトリ構造:

project-root/
├── src/
│   ├── main/
│   │   ├── java/
│   │   └── resources/
│   │       └── db/
│   │           └── migration/
│   │               ├── V1__Initial_schema.sql
│   │               ├── V2__Add_users_table.sql
│   │               ├── V3__Create_products_table.sql
│   │               ├── V4__Add_indexes.sql
│   │               └── R__Refresh_views.sql
│   └── test/
│       └── resources/
│           └── db/
│               └── migration/
│                   └── test/
│                       ├── V1__Initial_schema.sql
│                       └── V2__Insert_test_data.sql
└── flyway.conf

4.2.2 設定ファイル(flyway.conf)

プロジェクト直下にflyway.confを作成:

# Database接続設定
flyway.url=jdbc:postgresql://localhost:5432/myapp_dev
flyway.user=postgres
flyway.password=your_password
flyway.driver=org.postgresql.Driver

# マイグレーション設定
flyway.locations=filesystem:src/main/resources/db/migration
flyway.baselineOnMigrate=false
flyway.baselineVersion=0

# マイグレーション実行設定
flyway.validateOnMigrate=true
flyway.cleanDisabled=true
flyway.outOfOrder=false

# スキーマ・テーブル設定
flyway.schemas=public
flyway.table=flyway_schema_history

# ロギング設定
flyway.placeholderReplacement=true
flyway.placeholders.app_version=1.0.0
flyway.placeholders.migration_user=db_admin

4.2.3 環境別設定

異なる環境用設定ファイル:

flyway-dev.conf

flyway.url=jdbc:postgresql://localhost:5432/myapp_dev
flyway.user=dev_user
flyway.password=dev_password
flyway.validateOnMigrate=true
flyway.cleanDisabled=false

flyway-prod.conf

flyway.url=jdbc:postgresql://prod-db.company.com:5432/myapp
flyway.user=prod_user
flyway.password=${DATABASE_PASSWORD}
flyway.validateOnMigrate=true
flyway.cleanDisabled=true
flyway.outOfOrder=false

実行時に環境別設定を指定:

flyway -configFiles=flyway-prod.conf migrate

5. マイグレーション戦略

5.1 バージョニング戦略

5.1.1 セマンティックバージョニング型

メジャー.マイナー.パッチ形式:

V1__Initial_schema.sql
V1.1__Add_column_to_users.sql
V1.2__Create_index.sql
V2__Major_schema_redesign.sql
V2.1__Fix_column_type.sql

用途:

  • 大きな機能追加 → メジャー版アップ
  • スキーマ拡張 → マイナー版アップ
  • バグ修正・最適化 → パッチ版アップ

5.1.2 タイムスタンプ型

YYYYMMDDHHmmss形式:

V20240101120000__Create_users_table.sql
V20240105143000__Add_products_table.sql
V20240110090000__Create_indexes.sql

利点:

  • 実行時刻の追跡が容易
  • 並列開発で競合しにくい
  • Git履歴との対応づけが明確

欠点:

  • バージョン番号が長い
  • 意図不明確(何の変更かが見づらい)

5.1.3 チケット番号型

チケット・イシュー番号を含める:

V1__PROJ-123_Create_users_table.sql
V2__PROJ-456_Add_email_validation.sql
V3__PROJ-789_Create_audit_log.sql

利点:

  • 変更理由の追跡が容易
  • プロジェクト管理ツールとの連携
  • チェンジログ自動生成が可能

5.2 スキーマ進化パターン

5.2.1 テーブル追加パターン

-- V1__Create_initial_schema.sql
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- V2__Create_posts_table.sql
CREATE TABLE posts (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id),
    title VARCHAR(500) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- V3__Create_comments_table.sql
CREATE TABLE comments (
    id BIGSERIAL PRIMARY KEY,
    post_id BIGINT NOT NULL REFERENCES posts(id),
    user_id BIGINT NOT NULL REFERENCES users(id),
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

5.2.2 カラム追加パターン

-- V4__Add_profile_to_users.sql
ALTER TABLE users ADD COLUMN profile_bio TEXT;
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500);
ALTER TABLE users ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;

-- V5__Add_indexes_for_performance.sql
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_comments_post_id ON comments(post_id);

5.2.3 カラム削除パターン(推奨方法)

段階的削除パターン:

-- V6__Deprecate_unused_column.sql
-- Step 1: アプリケーション側でカラムの使用を停止
-- (マイグレーション側では何もしない、またはコメント)
-- ALTER TABLE users DROP COLUMN deprecated_field;

-- V7__Remove_deprecated_column.sql
-- Step 2: 十分な期間(1-2スプリント)経過後に削除
ALTER TABLE users DROP COLUMN deprecated_field;

注意: 本番環境でのカラム削除は慎重に

5.2.4 制約変更パターン

-- V8__Add_unique_constraint.sql
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE(email);

-- V9__Add_not_null_constraint.sql
-- NOT NULLの追加は段階的に
UPDATE users SET phone_number = '' WHERE phone_number IS NULL;
ALTER TABLE users ADD CONSTRAINT nn_phone_number CHECK(phone_number IS NOT NULL);

5.2.5 データ型変更パターン

-- V10__Change_column_type.sql
-- PostgreSQL例
ALTER TABLE products 
ALTER COLUMN price TYPE NUMERIC(10,2) USING price::NUMERIC(10,2);

-- MySQL例
ALTER TABLE products 
MODIFY COLUMN price DECIMAL(10,2);

5.3 並行開発での マイグレーション管理

5.3.1 マージコンフリクト回避

チーム開発でのシナリオ:

開発者A: 機能ブランチA作成
  └─ V1__Initial.sql存在

開発者B: 機能ブランチB作成
  └─ V1__Initial.sql存在

開発者A: V2__Feature_A.sqlを作成・コミット
開発者B: V2__Feature_B.sqlを作成・コミット

結果: バージョン競合 (V2が2つ存在)

解決策: タイムスタンプ型またはチケット型を使用

開発者A: V20240110120000__Feature_A.sql
開発者B: V20240110140000__Feature_B.sql

またはチケット型:
開発者A: V2__PROJ-123_Feature_A.sql
開発者B: V3__PROJ-456_Feature_B.sql

5.3.2 リベース時の対応

# ローカルでリベース前
flyway clean  # テスト環境でクリーニング

git rebase main

# リベース後、新マイグレーションの整序確認
flyway validate

# マイグレーション再実行
flyway migrate

5.4 ロールバック戦略

5.4.1 推奨: 新マイグレーションでの修正

-- V1__Add_column.sql (失敗した変更)
ALTER TABLE users ADD COLUMN age INT NOT NULL DEFAULT 0;

-- V2__Fix_previous_error.sql (修正)
ALTER TABLE users DROP COLUMN age;
ALTER TABLE users ADD COLUMN age INT DEFAULT NULL;

5.4.2 バージョンマッピング方式

-- V1__Initial_schema.sql
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE
);

-- V2__Add_age_column.sql (失敗)
ALTER TABLE users ADD COLUMN age INT NOT NULL;

-- V3__Remove_age_column.sql (修正)
ALTER TABLE users DROP COLUMN age;

6. 実践的な設定例

6.1 Spring Bootアプリケーション統合

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>flyway-demo</artifactId>
    <version>1.0.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
    </parent>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- Flyway -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <configuration>
                    <url>${flyway.url}</url>
                    <user>${flyway.user}</user>
                    <password>${flyway.password}</password>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/myapp
    username: postgres
    password: password
    driver-class-name: org.postgresql.Driver

  flyway:
    enabled: true
    locations: classpath:db/migration
    baselineOnMigrate: false
    validateOnMigrate: true
    cleanDisabled: true
    outOfOrder: false
    encoding: UTF-8
    sqlMigrationPrefix: V
    repeatableSqlMigrationPrefix: R

logging:
  level:
    org.flywaydb: INFO

アプリケーション起動時の動作

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// 起動時にFlywayが自動実行される
// 1. Flyway初期化
// 2. マイグレーション検出・検証
// 3. 未実行マイグレーションを順序に実行
// 4. アプリケーション起動

6.2 マルチスキーマ対応

複数のスキーマを管理する場合の設定:

application.yml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/myapp
    username: postgres
    password: password

  flyway:
    schemas: schema1,schema2,schema3
    baselineOnMigrate: true
    validateOnMigrate: true

    # スキーマ1用マイグレーション
    locations:
      - classpath:db/migration/schema1
      - classpath:db/migration/schema2

マイグレーションファイル構成

src/main/resources/db/migration/
├── schema1/
│   ├── V1__Create_schema1_tables.sql
│   └── V2__Add_schema1_indexes.sql
└── schema2/
    ├── V1__Create_schema2_tables.sql
    └── V2__Add_schema2_indexes.sql

SQL例

-- V1__Create_schema1_tables.sql
CREATE SCHEMA IF NOT EXISTS schema1;

CREATE TABLE schema1.users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);

-- V1__Create_schema2_tables.sql
CREATE SCHEMA IF NOT EXISTS schema2;

CREATE TABLE schema2.products (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);

6.3 プレースホルダー(変数置換)

動的な値をマイグレーション内で使用:

flyway.conf

flyway.placeholderReplacement=true
flyway.placeholders.app_user=app_user
flyway.placeholders.admin_user=admin_user
flyway.placeholders.schema=public

マイグレーションSQL

-- V1__Create_users_and_roles.sql
CREATE TABLE ${schema}.users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);

-- 権限設定
GRANT SELECT, INSERT, UPDATE ON ${schema}.users TO ${app_user};
GRANT ALL PRIVILEGES ON ${schema}.users TO ${admin_user};

実行時の変数値指定:

flyway -placeholders.app_user=myapp \
       -placeholders.admin_user=admin \
       -placeholders.schema=public \
       migrate

6.4 環境別マイグレーション

異なる環境(開発・ステージング・本番)で異なるマイグレーションを実行:

ディレクトリ構成

src/main/resources/db/migration/
├── V1__Initial_schema.sql          # 全環境
├── V2__Create_base_tables.sql      # 全環境
└── dev/
    ├── V100__Create_test_data.sql
    └── V101__Create_dev_views.sql

Spring Profileの使用

# application-dev.yml
spring:
  flyway:
    locations:
      - classpath:db/migration
      - classpath:db/migration/dev

# application-prod.yml
spring:
  flyway:
    locations:
      - classpath:db/migration
    # devディレクトリは含まない

実行方法:

# 開発環境
java -jar app.jar --spring.profiles.active=dev

# 本番環境
java -jar app.jar --spring.profiles.active=prod

7. マイグレーションバージョニング

7.1 バージョン番号の形式

Flywayが認識するバージョン番号:

1                    (単一の数値)
1.0                  (ドット区切り)
1.0.1                (複数レベル)
2023.01.15           (年月日)
1.2.3.4.5.6          (複数レベルのドット)

制限事項:

  • 最初の文字は数値でなければならない
  • ドット、アンダースコアのみ使用可能
  • 数値のみで構成(プリフィックス・サフィックスなし)

7.2 チェックサム機構

マイグレーションファイル変更検出メカニズム:

7.2.1 チェックサム計算方法

// Flywayの内部処理
MD5 checksum = MD5.calculate(sqlMigrationContent);

各マイグレーション実行時にファイルのMD5ハッシュを計算:

V1__Initial.sql の内容がファイルから計算 
  → MD5("CREATE TABLE users...")
  → "a1b2c3d4e5f6..."
  → flyway_schema_historyに記録

次回実行時に再度計算
  → チェックサムが一致 → スキップ
  → チェックサムが不一致 → エラー(破損警告)

7.2.2 チェックサムエラー対応

-- エラーが発生した場合
-- Error: Checksum mismatch of migration V1__Initial.sql

-- 修正方法1: マイグレーション新規作成(推奨)
-- V2__Fix_previous_migration.sql で修正内容を記述

-- 修正方法2: チェックサム再計算(非推奨)
-- データベースのflyway_schema_historyのchecksumをNULLに設定
UPDATE flyway_schema_history 
SET checksum = NULL 
WHERE version = '1';

7.3 バージョン競合解決

複数の開発者が同一バージョンのマイグレーションを作成した場合:

シナリオ1: 同じバージョン番号

開発者A: V2__Feature_A.sql
開発者B: V2__Feature_B.sql

エラー:
org.flywaydb.core.api.FlywayException: 
  Detected version duplicates: V2

解決方法: リベースして番号を付け直す

開発者A: V2__Feature_A.sql (チェックアウト)
開発者B: V3__Feature_B.sql (リネーム)

シナリオ2: マージ後の不整合

# Git上の順序
V1__Initial.sql
V2__Feature_B.sql (新規)
V3__Feature_A.sql (リネーム)

# しかし実行順序は V1 → V2 → V3 → ...
# チェックサムが全て保存されるため競合なし

8. 本番環境への適用

8.1 本番環境での安全対策

8.1.1 設定のベストプラクティス

# 本番環境用 flyway-prod.conf

# Database接続 - 環境変数から読み込み
flyway.url=${DATABASE_URL}
flyway.user=${DATABASE_USER}
flyway.password=${DATABASE_PASSWORD}

# セキュリティ設定
flyway.cleanDisabled=true          # テーブルクリーニング禁止
flyway.outOfOrder=false            # 順序外実行禁止
flyway.validateOnMigrate=true      # 事前検証を実施

# トランザクション設定
flyway.validateOnMigrate=true
flyway.placeholderReplacement=true

# スキーマ履歴
flyway.table=flyway_schema_history
flyway.schemas=public

# ロギング
flyway.loggers=org.flywaydb

8.1.2 実行前チェックリスト

本番環境へのマイグレーション実行前:

  • ステージング環境で完全テスト実施
  • マイグレーションのバックアップ確認
  • ロールバック手順の検証
  • マイグレーション実行時間の推定
  • ダウンタイムの予測と通知
  • 実行ユーザーの権限確認
  • 本番DBへの最新バックアップ作成
  • 監視システムの確認

8.2 CI/CDパイプライン統合

8.2.1 GitHubActions統合例

# .github/workflows/database-migration.yml
name: Database Migration

on:
  push:
    branches: [main]
    paths:
      - 'src/main/resources/db/migration/**'
  pull_request:
    paths:
      - 'src/main/resources/db/migration/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v3

      - name: Set up Java
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'

      - name: Validate migrations
        run: |
          mvn flyway:validate \
            -Dflyway.url=jdbc:postgresql://localhost:5432/testdb \
            -Dflyway.user=postgres \
            -Dflyway.password=postgres

      - name: Run migrations
        run: |
          mvn flyway:migrate \
            -Dflyway.url=jdbc:postgresql://localhost:5432/testdb \
            -Dflyway.user=postgres \
            -Dflyway.password=postgres

      - name: Test migration info
        run: |
          mvn flyway:info \
            -Dflyway.url=jdbc:postgresql://localhost:5432/testdb \
            -Dflyway.user=postgres \
            -Dflyway.password=postgres

  migrate:
    needs: validate
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment:
      name: production

    steps:
      - uses: actions/checkout@v3

      - name: Set up Java
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'

      - name: Execute migrations
        run: |
          mvn flyway:migrate \
            -Dflyway.url=${{ secrets.PROD_DB_URL }} \
            -Dflyway.user=${{ secrets.PROD_DB_USER }} \
            -Dflyway.password=${{ secrets.PROD_DB_PASSWORD }}

      - name: Post migration info
        run: |
          mvn flyway:info \
            -Dflyway.url=${{ secrets.PROD_DB_URL }} \
            -Dflyway.user=${{ secrets.PROD_DB_USER }} \
            -Dflyway.password=${{ secrets.PROD_DB_PASSWORD }}

8.2.2 GitLab CI統合例

# .gitlab-ci.yml
stages:
  - validate
  - migrate

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

validate:migration:
  stage: validate
  image: maven:3.9-eclipse-temurin-17
  services:
    - postgres:15
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: postgres
  script:
    - mvn clean flyway:validate flyway:migrate
      -Dflyway.url=jdbc:postgresql://postgres:5432/testdb
      -Dflyway.user=postgres
      -Dflyway.password=postgres

migrate:production:
  stage: migrate
  image: maven:3.9-eclipse-temurin-17
  only:
    - main
  when: manual
  script:
    - mvn flyway:migrate
      -Dflyway.url=$PROD_DB_URL
      -Dflyway.user=$PROD_DB_USER
      -Dflyway.password=$PROD_DB_PASSWORD

8.3 障害対応

8.3.1 マイグレーション失敗時対応

シナリオ1: 構文エラー

-- V5__Add_column.sql(失敗)
ALTER TABLE users ADD COLUMN invalid_date; -- エラー: 型指定なし

-- 対応: V6で修正
-- V6__Fix_column_definition.sql
ALTER TABLE users ADD COLUMN event_date TIMESTAMP;

シナリオ2: 権限エラー

Error: Permission denied on table users

対応:

  • 接続ユーザーの権限確認
  • テーブルの所有者確認
  • SELECTグラント、ALTERグラント確認
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO migration_user;
GRANT USAGE, CREATE ON SCHEMA public TO migration_user;

シナリオ3: トランザクション失敗

Error: Transaction rollback due to constraint violation

対応:

  • NOT NULL制約への対応
  • 一意制約への対応
  • 外部キー制約への対応
-- 段階的実行

-- Step 1: デフォルト値を設定
UPDATE users SET status = 'ACTIVE' WHERE status IS NULL;

-- Step 2: 制約追加
ALTER TABLE users ADD CONSTRAINT nn_status 
  CHECK(status IS NOT NULL);

8.3.2 ロールバック手順

本番環境でのロールバック(必要な場合):

# 1. 現在のマイグレーション状態確認
flyway -url=$PROD_DB_URL info

# 2. テーブルから不正なレコード削除
DELETE FROM flyway_schema_history 
WHERE version = '5' AND success = false;

# 3. バックアップから復元(必要な場合)
# pg_restore -d myapp backup.dump

# 4. バージョン検証
flyway -url=$PROD_DB_URL validate

# 5. 修正版マイグレーション作成
# V6__Fix_previous_error.sql を新規作成

# 6. 再度実行
flyway -url=$PROD_DB_URL migrate

9. トラブルシューティング

9.1 よくあるエラーと対応

9.1.1 "Checksum mismatch" エラー

Error: Checksum mismatch of migration V1__Create_table.sql
  Calculated checksum: a1b2c3d4e5f6
  Stored checksum:     x9y8z7w6v5u4

原因: マイグレーションファイルが実行後に変更された

対応:

-- オプション1: ファイルを元に戻す
git checkout db/migration/V1__Create_table.sql

-- オプション2: 新しいマイグレーションで修正
-- V2__Fix_v1_changes.sql を作成

-- オプション3: チェックサムを更新(本番環境以外)
UPDATE flyway_schema_history 
SET checksum = (SELECT CHECKSUM FROM ...)
WHERE version = '1';

9.1.2 "Version duplicates" エラー

Error: Detected version duplicates: V2
  - V2__Feature_A.sql
  - V2__Feature_B.sql

原因: 同じバージョン番号のマイグレーションが複数存在

対応:

# 1. ファイル確認
ls src/main/resources/db/migration/ | grep V2

# 2. ファイルリネーム
mv V2__Feature_B.sql V3__Feature_B.sql

# 3. Flywayコマンド再実行
flyway migrate

9.1.3 "Schema does not exist" エラー

Error: PostgreSQL JDBC Driver: 
  ERROR: schema "myschema" does not exist

原因: マイグレーション対象スキーマが存在しない

対応:

-- V1__Create_schema.sql
CREATE SCHEMA IF NOT EXISTS myschema;

-- または flyway.conf で設定
flyway.schemas=myschema

9.1.4 "Table 'flyway_schema_history' already exists" エラー

Error: 
  Flyway table 'flyway_schema_history' already exists
  in database

原因: Flywayが複数回初期化された

対応:

# 1. 既存テーブル確認
SELECT * FROM flyway_schema_history;

# 2. テーブルが正常 → 無視して続行
flyway -baselineOnMigrate=true migrate

# 3. テーブル破損の場合 → 再作成
DROP TABLE IF EXISTS flyway_schema_history;
flyway migrate

9.2 パフォーマンス最適化

9.2.1 大規模マイグレーションの最適化

-- V1__Bulk_insert_optimization.sql
-- 避けるべき
-- INSERT INTO large_table VALUES (...);
-- INSERT INTO large_table VALUES (...);
-- (数百万行)

-- 推奨: COPY コマンド(PostgreSQL)
\COPY large_table FROM '/tmp/data.csv' WITH (FORMAT csv);

-- または LOAD DATA(MySQL)
LOAD DATA INFILE '/tmp/data.csv'
INTO TABLE large_table
FIELDS TERMINATED BY ',';

-- またはバルク挿入
INSERT INTO large_table SELECT * FROM staging_table;

9.2.2 インデックス作成の最適化

-- V2__Create_indexes_concurrently.sql

-- PostgreSQL: 本番テーブル以外で発生

-- 同時実行インデックス作成(ロック最小化)
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- MySQL: 並列処理
ALTER TABLE users ADD INDEX idx_email (email), ALGORITHM=INPLACE, LOCK=NONE;

9.3 デバッグ手法

9.3.1 ログレベルの調整

# flyway.conf
flyway.logger=slf4j

# logback.xml
<configuration>
  <logger name="org.flywaydb" level="DEBUG" />
  <logger name="org.flywaydb.core.internal.command.DbMigrate" level="TRACE" />
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

9.3.2 SQLスクリプト検証

マイグレーション実行前に個別検証:

# 1. スクリプトをファイルに保存
cat src/main/resources/db/migration/V1__Initial.sql > /tmp/test.sql

# 2. 直接実行
psql -h localhost -U postgres -d testdb -f /tmp/test.sql

# 3. 文法チェック(MySQLの場合)
mysql -h localhost -u root -p testdb < /tmp/test.sql

10. ベストプラクティス

10.1 マイグレーション設計

10.1.1 原子性の保証

各マイグレーションは単一の独立した変更を表現:

-- 悪い例: 複数の論理的変更を1つのマイグレーションに
-- V1__Add_users_and_posts_tables.sql
CREATE TABLE users (...);
CREATE TABLE posts (...);
ALTER TABLE posts ADD CONSTRAINT fk_user REFERENCES users(id);
ALTER TABLE posts ADD INDEX idx_user_id ON user_id;

-- 良い例: 論理的に分割
-- V1__Create_users_table.sql
CREATE TABLE users (...);

-- V2__Create_posts_table.sql
CREATE TABLE posts (...);

-- V3__Add_foreign_key_constraint.sql
ALTER TABLE posts ADD CONSTRAINT fk_user REFERENCES users(id);

-- V4__Add_indexes.sql
ALTER TABLE posts ADD INDEX idx_user_id ON user_id;

10.1.2 イディオマティックなSQLの使用

-- 良い例: 環境に応じた条件付き実行
-- V1__Create_table_if_not_exists.sql

-- PostgreSQL
CREATE TABLE IF NOT EXISTS users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE
);

-- MySQL
CREATE TABLE IF NOT EXISTS users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE
);

-- Oracle
BEGIN
  EXECUTE IMMEDIATE 'CREATE TABLE users (
    id NUMBER PRIMARY KEY,
    username VARCHAR2(255) NOT NULL UNIQUE
  )';
EXCEPTION
  WHEN OTHERS THEN NULL;
END;

10.1.3 データベース固有の機能使用

-- PostgreSQL特有
-- V1__Create_enum_type.sql
CREATE TYPE status_enum AS ENUM('ACTIVE', 'INACTIVE', 'PENDING');

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    status status_enum DEFAULT 'ACTIVE'
);

-- MySQL特有
-- V1__Create_table_with_partitioning.sql
CREATE TABLE events (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    event_date DATE NOT NULL,
    data JSON
) PARTITION BY RANGE(YEAR(event_date)) (
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION pmax VALUES LESS THAN MAXVALUE
);

10.2 ドキュメンテーション

10.2.1 マイグレーション記述規則

-- V1__Create_users_table.sql
-- 
-- 目的: ユーザー管理テーブルの初期構築
-- 
-- 作成者: DB Team
-- 作成日: 2024-01-10
-- 
-- 説明:
-- ユーザー認証・管理用のメインテーブル
-- 以下のカラムで構成:
-- - id: ユニークな識別子(主キー)
-- - username: ユーザー名(一意)
-- - email: メールアドレス(一意)
-- - created_at: 作成日時
-- - updated_at: 更新日時

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- インデックス作成
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);

10.2.2 マイグレーションログ管理

# マイグレーション実行ログの記録
flyway info > migration_log_$(date +%Y%m%d_%H%M%S).txt

# 重要な変更のみ抽出
grep "SUCCESS" migration_log_*.txt | tail -20

10.3 チーム運用

10.3.1 レビュー・チェックリスト

マイグレーション作成時のレビューポイント:

  • ファイル名規則に従っているか(V番号の連続性)
  • SQLは対象DBで正常に実行可能か
  • データ損失の可能性がないか
  • バックアップの必要性検討
  • パフォーマンス影響がないか
  • ロールバック方法が明確か
  • ドキュメント・コメント完備か
  • 他マイグレーションとの依存関係チェック
  • 本番環境での実行時間推定
  • アラート・モニタリング設定

10.3.2 コミット規則

# 良い例のコミットメッセージ
git commit -m "db: add user profile fields [V5]

- Add profile_bio column to users table
- Add avatar_url column to users table
- Add updated_at timestamp
- Create index on profile_bio for search

Migration: V5__Add_profile_fields_to_users.sql
Execution time (estimated): 50ms
"

10.3.3 マイグレーションの命名規則統一

チーム全体で統一した命名規則の採用:

推奨: セマンティックバージョン
V1__Create_users_table.sql
V1.1__Add_email_column_to_users.sql
V2__Create_products_table.sql

または チケット番号ベース
V1__PROJ-100_Initialize_schema.sql
V2__PROJ-101_Add_user_profile_fields.sql

10.4 本番環境運用

10.4.1 本番環境でのマイグレーション実行戦略

#!/bin/bash
# 本番環境マイグレーション実行スクリプト

set -e

DB_URL="jdbc:postgresql://prod-db:5432/myapp"
DB_USER="${PROD_DB_USER}"
DB_PASSWORD="${PROD_DB_PASSWORD}"

echo "=== マイグレーション情報確認 ==="
flyway -url=$DB_URL -user=$DB_USER -password=$DB_PASSWORD info

echo ""
echo "=== マイグレーション検証 ==="
flyway -url=$DB_URL -user=$DB_USER -password=$DB_PASSWORD validate

echo ""
echo "=== 事前チェック ==="
read -p "本番環境へのマイグレーション実行を確認してください (y/n): " confirm
if [ "$confirm" != "y" ]; then
  echo "中止しました"
  exit 1
fi

echo ""
echo "=== マイグレーション実行開始 ==="
START_TIME=$(date +%s)

flyway -url=$DB_URL -user=$DB_USER -password=$DB_PASSWORD migrate

END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))

echo ""
echo "=== マイグレーション完了 ==="
echo "実行時間: ${DURATION}秒"
echo ""
flyway -url=$DB_URL -user=$DB_USER -password=$DB_PASSWORD info

10.4.2 ロールフォワード戦略

失敗時の対応:

-- マイグレーション失敗で取り消す必要がある場合
-- (通常は新マイグレーションで修正を推奨)

-- 1. 失敗したマイグレーションを履歴から削除
DELETE FROM flyway_schema_history 
WHERE version = '5' AND success = false;

-- 2. 新しいマイグレーション作成
-- V5__Fix_previous_error.sql

-- 3. 再実行
flyway migrate

まとめ

Flywayはシンプルかつ強力なデータベースマイグレーションツールです。主な要点:

  1. シンプルな設計: SQLファイルを配置するだけでマイグレーション実行
  2. バージョン管理: すべての変更履歴を自動追跡
  3. 安全な実行: トランザクション・検証・チェックサム機構
  4. 柔軟な統合: JVM・CLI・Docker・CI/CDに対応
  5. 本番対応: 豊富な設定オプションで様々なシナリオに対応

チーム開発では以下の点を重視:

  • 統一された命名規則
  • 論理的な変更単位への分割
  • 十分なドキュメンテーション
  • 段階的な検証・テスト
  • 本番環境での慎重な実行

Flywayを正しく運用することで、データベーススキーマのバージョン管理はアプリケーションコードと同様に安全かつ予測可能になります。