Flywayによるデータベーススキーママイグレーションの自動化:PostgreSQL、MySQL、CI/CD環境の構築

Database tutorial - IT technology blog
Database tutorial - IT technology blog

背景と理由:深夜のSQL直接修正からの脱却

昨年の初頭、私たちのリリースプロセスには重大な問題点がありました。それは手作業によるスキーマ更新です。開発者がSlackのスレッドにマイグレーション用のSQLスニペットを貼り付け、オンコールエンジニアがメンテナンス時間枠に本番ノード上でそれらを手動実行していました。約35個のマイクロサービスと120個のデータベーステーブルを抱える規模になると、ヒューマンエラーは避けられなくなりました。インデックスの追加が漏れたり、外部キーのパッチを誤った順序で実行してしまったりしたことで、単一の四半期だけで3件もの大規模なロールバック障害が発生したのです。

私たちはデータベースの状態をGit上のコードと一緒に管理する必要がありました。さらに重要なのは、ローカルのDocker環境からマルチノードのステージング環境、本番クラスタに至るすべての環境で、マージ時にスキーマを決定論的(確実)に更新できることでした。この要件を満たすために選定したのがFlywayです。

Flywayは、構造化されたファイル名を持つプレーンなSQLスクリプトを使用して適用済みの変更を追跡します。データベースに対して実行されると、flyway_schema_historyというメタデータテーブルを確認します。このテーブルには、スクリプトのバージョン、実行ステータス、実行時間(ミリ秒)、およびCRC32チェックサムが保存されます。もしGit上で適用済みのマイグレーションスクリプトが改変された場合、Flywayはチェックサムの不一致を検出し、新しい変更を適用する前に即座にビルドを失敗させます。

インストール:ローカル開発環境とコンテナランタイム

Flywayは、スタンドアロンバイナリ、ビルドツールのプラグイン(Maven、Gradle)、または公式Dockerイメージを通じて実行できます。ローカル開発ではパッケージマネージャーを使うことで即座に使い始めることができます。自動化されたCI/CDランナーでは、コンテナイメージを使用することで、ローカルドライバへの依存なしに一貫した環境を提供できます。

方法1:CLIの直接インストール

macOSおよびLinuxの開発環境では、スタンドアロンバイナリを直接インストールします。

# Homebrewを使用したmacOSでのインストール
brew install flyway

# Linux x64 CLIの手動ダウンロード
wget -qO- https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.10.0/flyway-commandline-10.10.0-linux-x64.tar.gz | tar xvz
sudo ln -s $(pwd)/flyway-10.10.0/flyway /usr/local/bin/flyway

# インストールの検証
flyway -v

方法2:Docker経由での実行

公式Dockerイメージには、PostgreSQL、MySQL、MariaDB、Oracle用の設定済みJDBCドライバがバンドルされています。マイグレーションディレクトリと設定フォルダを直接マウントして実行します。

docker run --rm \
  -v $(pwd)/sql/migrations:/flyway/sql \
  -v $(pwd)/conf:/flyway/conf \
  flyway/flyway:10.10.0 info

設定:スクリプトの命名規則、複数エンジンの構成、ベースライン

Flywayは、実行順序とスクリプトの動作を決定するために厳格な命名規則を採用しています。

マイグレーションファイルの命名規則

Flywayはマイグレーションディレクトリをスキャンし、プレフィックスに基づいてファイルを分類します。

  • バージョン管理マイグレーション(V<Version>__<Description>.sql):厳密な昇順で1回のみ適用されます(例:V1.0__create_users_table.sql、V1.1__add_user_profile.sql)。
  • リピータブルマイグレーション(R__<Description>.sql):すべてのバージョン管理マイグレーションが完了した後に適用されます。ファイルの内容やチェックサムが変更されるたびに自動的に再実行されます(ビュー、ストアドプロシージャ、トリガーに最適です)。
  • アンドゥマイグレーション(U<Version>__<Description>.sql):特定のバージョン管理による変更を取り消します。なお、アンドゥ機能を利用するにはFlyway TeamsまたはEnterpriseのライセンスが必要です。

データベース接続の設定

接続パラメータはflyway.confファイルに保存するか、環境変数(FLYWAY_URL、FLYWAY_USER、FLYWAY_PASSWORD)経由で渡します。標準的な設定ファイルの例は以下のとおりです。

# PostgreSQL設定
flyway.url=jdbc:postgresql://localhost:5432/app_production
flyway.user=postgres_user
flyway.password=secure_password
flyway.schemas=public
flyway.locations=filesystem:./sql/migrations

# 既存データベース向けのベースライン設定
flyway.baselineOnMigrate=true
flyway.baselineVersion=0.0
flyway.baselineDescription=Initial baseline

# MySQL設定例
# flyway.url=jdbc:mysql://localhost:3306/app_production?useSSL=false&allowPublicKeyRetrieval=true
# flyway.user=mysql_user
# flyway.password=secure_password
# flyway.schemas=app_production

マイグレーションスクリプトの作成

DDLの変更はアトミック(不可分)かつ各DBの方言(ダイアレクト)に合わせて記述します。以下はsql/migrations/V1.0__init_schema.sqlに記述するPostgreSQLの初期スキーマの例です。

-- PostgreSQL初期ユーザーテーブル
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);

続いて、sql/migrations/V1.1__add_user_profile.sqlで差分の追加を行います。

ALTER TABLE users ADD COLUMN full_name VARCHAR(120);
ALTER TABLE users ADD COLUMN metadata JSONB;

初期マイグレーションやシードスクリプトの準備中、モックのユーザーデータが一括形式で提供されるケースによく遭遇します。データインポートのためにCSVをJSONへ素早く変換したいときは、ブラウザ上で動作しデータがマシン外に出ない toolcraft.app/ja/tools/data/csv-to-json を使用しています。マイグレーションテストのシード用JSONペイロードを手軽に準備できるため重宝しています。

GitHub Actionsでのマイグレーション自動化

マルチレプリカ環境では、アプリケーションの起動フックからスキーママイグレーションを実行してはいけません。3つのKubernetes Podが同時に起動して並行してマイグレーションを試みると、競合状態(レースコンディション)が発生するリスクがあります。代わりに、アプリケーションコードをデプロイする前の専用パイプラインステップとしてマイグレーションを実行します。

name: データベースマイグレーションのデプロイ

on:
  push:
    branches:
      - main
    paths:
      - 'sql/migrations/**'

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - name: コードのチェックアウト
        uses: actions/checkout@v4

      - name: Flywayマイグレーションの実行
        uses: docker://flyway/flyway:10.10.0
        with:
          args: >-
            -url="${{ secrets.DB_JDBC_URL }}"
            -user="${{ secrets.DB_USER }}"
            -password="${{ secrets.DB_PASSWORD }}"
            -locations="filesystem:./sql/migrations"
            migrate

検証、トラブルシューティング、リカバリ

データベースマイグレーションでは、CIのプルリクエストにおける事前のバリデーションと、構文エラーが混入してしまった際の明確なリカバリ手順が不可欠です。

1. マイグレーション状態の確認

どのマイグレーションが適用済みで、どれが保留中かを確認します。

flyway info

CLIによって以下のような表形式のログが出力されます。

+-----------+---------+--------------------+------+---------------------+---------+
| カテゴリ  | バージョン | 説明               | タイプ | 適用日時            | 状態    |
+-----------+---------+--------------------+------+---------------------+---------+
| Versioned | 1.0     | init schema        | SQL  | 2026-03-15 10:20:00 | Success |
| Versioned | 1.1     | add user profile   | SQL  | 2026-04-02 08:14:22 | Success |
| Versioned | 1.2     | add audit triggers | SQL  |                     | Pending |
+-----------+---------+--------------------+------+---------------------+---------+

2. flyway validateによるファイルの誤編集の検出

すべてのプルリクエストのチェックにflyway validateを追加します。開発者が新しいバージョンを作成する代わりに過去のマイグレーションファイルを編集してしまった場合でも、Flywayはステージングや本番データベースに影響を与える前にビルドを失敗させます。

flyway validate

バリデーションによってチェックサムの不一致が検出されると、以下のような出力が表示されます。

エラー: 検証に失敗しました: マイグレーションの検証に失敗しました
バージョン 1.1 のマイグレーションでチェックサムが一致しません
- データベース適用値 : -1829384912
- ローカル解決値     : 1928374615

3. 失敗したマイグレーションからのリカバリ

マイグレーション失敗からのリカバリ手順は、使用しているデータベースエンジンによって異なります。PostgreSQLはDDL文をトランザクション内で実行するため、エラーが発生した場合は部分的な変更を自動的にロールバックします。しかし、MySQLはトランザクショナルDDLをサポートしていません。MySQLでスクリプトが途中で失敗した場合、そのファイル内のそれより前のステートメントはコミットされたままとなり、FlywayはマイグレーションをFailed(失敗)として記録します。

失敗した状態から復旧するには:

  1. データベースを手動で確認し、未コミットまたは部分的に適用されたDDLステートメントをロールバックします。
  2. マイグレーションスクリプト内の根本原因を修正します。
  3. flyway repairを実行してflyway_schema_historyから失敗したエントリを削除し、チェックサムを再計算します。
flyway repair

修復が完了したら、再度flyway migrateを実行します。CIでの自動検証と明確なパイプラインのトリガー設定により、デプロイごとのスキーマ同期は5秒未満で完了し、インフラ全体でデータベースの状態を常に整合性の取れた状態に保つことができます。

Share: