FlywayとLiquibase:実プロジェクトにおけるデータベースの自動マイグレーション

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

データベーススキーマ変更を自動でナビゲートする

開発、ステージング、本番環境にわたるデータベーススキーマの変更管理は、綱渡りのように感じられることがあります。カラムの見落とし、インデックスの忘れ、またはスクリプトの誤適用は、ダウンタイム、データ破損、あるいは厄介なバグに簡単につながる可能性があります。

幸いなことに、FlywayやLiquibaseのようなデータベースマイグレーションツールは、この重要なプロセスを自動化し、一貫性と信頼性を確保します。手動でSQLスクリプトを実行し、手順を忘れていないことを密かに願ったことがあるなら、この記事は間違いなくあなたのためです。

アプローチの比較:データベーススキーマ管理の選択肢

FlywayとLiquibaseについて深く掘り下げる前に、データベーススキーマ管理の一般的なアプローチを探ってみましょう。ほとんどのチームは通常、以下のいずれかのカテゴリに分類されます。

手動SQLスクリプト

多くのプロジェクトでは、手動SQLスクリプトがチームが最初に使用する方法となることがよくあります。開発者は、スキーマ変更ごとに個別の.sqlファイルを作成します。たとえば、V1__create_users_table.sqlV2__add_email_column.sqlといったファイルです。これらのスクリプトは、適切なデータベースに対して手動で実行されます。

  • 長所: 完全な制御が可能で、SQLの専門家にとっては学習曲線がほとんどありません。
  • 短所: ヒューマンエラー(例:間違ったスクリプトの実行や完全に忘れられたスクリプト)に非常に脆弱で、適用されたマイグレーションの追跡が困難になり、組み込みのロールバックメカニズムがなく、分散開発チームにとっては課題となります。

ORMベースのマイグレーション

Django ORM、Hibernate、Entity Frameworkなどの多くのオブジェクトリレーショナルマッパー(ORM)は、独自のマイグレーション機能を備えています。これらのORMは、アプリケーションのモデルやエンティティで検出された変更に基づいて、SQLスクリプトを自動的に生成することがよくあります。

  • 長所: アプリケーションコードと密接に連携し、スクリプトの自動生成機能を備え、全体的な開発ワークフローを簡素化します。
  • 短所: ストアドプロシージャやトリガーのような複雑なデータベース固有の機能に対しては、柔軟性が低下する可能性があります。場合によっては、生成されるSQLが最適でないことがあります。例えば、ORMが効率性のためにカラム追加をまとめて処理する代わりに、個々のALTER TABLEステートメントを作成する場合があります。さらに、ORMマイグレーションに大きく依存すると、特定のORMにロックインされ、将来的にテクノロジーを切り替えることが難しくなる可能性があります。

専用データベースマイグレーションツール (Flyway & Liquibase)

対照的に、FlywayやLiquibaseのような専用ツールは、データベーススキーマ管理に完全に焦点を当てています。これらは、ほぼすべてのデータベースおよびアプリケーションスタックと互換性のある言語非依存のソリューションを提供します。これらのツールは、適用された変更を追跡し、バージョン管理を行い、マイグレーションの適用(および時にはロールバック)のための強力なメカニズムを提供します。

  • 長所: データベース非依存で、堅牢なバージョン管理を提供し、強力な共同開発サポートを提供し、複雑なスキーマ変更の処理に優れ、明確で監査可能な変更履歴を維持します。
  • 短所: ビルドパイプラインに追加のツールを組み込む必要があり、その特定の規則を理解するためのわずかな学習曲線が伴います。

長所と短所:Flyway vs. Liquibase

FlywayとLiquibaseはどちらも同じ中心的な問題を解決しますが、それぞれ異なる哲学と基盤となるメカニズムでアプローチします。

Flyway

Flywayは「設定よりも規約」の原則に基づいて動作し、シンプルでバージョン管理されたSQLスクリプトを好みます。

  • 長所:
    • シンプルさ: SQLに慣れている場合、Flywayは驚くほど簡単に習得できます。単にプレーンなSQLファイルを記述し、バージョン番号(例:V1.0.1__create_users_table.sql)をプレフィックスとして付与するだけで、そこからFlywayが適用プロセスを管理します。
    • 予測可能性: マイグレーションの順序はファイル名によって厳密に決定されるため、フロー全体が非常に明確で追跡しやすくなります。
    • 軽量: オーバーヘッドが最小限で、多くの場合、アプリケーションの起動シーケンスに直接統合されます。
    • 純粋なSQLへの注力: データベース固有のSQLの記述を奨励し、選択したデータベースの最も高度な機能と最適化を活用できるようにします。
    • DMLのロールバック: Flywayは自動DDLロールバックを提供しませんが、必要に応じてデータ操作言語(DML)用の明示的なロールバックスクリプトを記述できます。あるいは、「フォワードオンリー」戦略でDDL変更を管理することもできます。
  • 短所:
    • SQL中心: チームがSQLに習熟していない場合や、より高レベルの抽象化レイヤーを好む場合、そのSQL中心の性質は大きな障害となります。
    • DDLの自動ロールバックなし: スキーマ(DDL)変更の場合、Flywayはロールバックスクリプトを自動的に生成しません。カスタムのrevertマイグレーションを適用するか、バックアップから復元するかのいずれかの方法で、ロールバックを手動で管理する必要があります。
    • 限定された抽象化: 同じコアマイグレーションロジックで複数のデータベースタイプをサポートする必要がある場合、各データベースベンダーに合わせた個別のSQLスクリプトを記述する必要があるかもしれません。

Liquibase

Liquibaseは、より抽象的でデータ駆動型のアプローチを採用しており、XML、YAML、JSON、あるいはプレーンなSQLなど、さまざまな形式を使用してマイグレーションを定義できます。

  • 長所:
    • データベース抽象化: チェンジセット形式により、一度マイグレーションを定義すれば、Liquibaseがそれを異なるデータベースタイプに対応する正しいSQLに変換します。これは、複数のデータベースベンダーを対象とする場合に特に大きな利点となります。
    • 複数の形式: チェンジログにはXML、YAML、JSON、またはプレーンなSQLの中から選択でき、多様なチームの好みやプロジェクト要件に対応します。
    • 自動ロールバック: Liquibaseは、多くの一般的な変更タイプに対してロールバックSQLを生成できるため、強力で自動化されたセーフティネットを提供します。
    • コンテキストとラベル: マイグレーションにタグを付けて、条件付きで適用できます(例:「test-data」は開発環境のみに適用、「hotfix」は本番環境のみに適用)。
    • リファクタリング機能: renameColumnaddForeignKeyConstraintなど、一般的なデータベースリファクタリング用の特定の組み込み変更タイプを提供します。
  • 短所:
    • 複雑さ: XML、YAML、またはJSONのチェンジログ形式は冗長であることが多く、単純なSQLファイルと比較して学習曲線がかなり急峻です。これらの宣言型形式内の問題のデバッグも、より複雑になる傾向があります。
    • 生成されたSQL: 抽象化機能は強力ですが、Liquibaseによって生成されるSQLは、常に最適な効率であるとは限らず、経験豊富なDBAが手動で記述する内容と正確に一致するとは限りません。
    • オーバーヘッド: 豊富な機能セットとより冗長な構成を考えると、特に非常に小規模なプロジェクトでは、Flywayよりも重く感じられる可能性があります。

推奨されるセットアップ:ツールの選択と統合

FlywayとLiquibaseの選択は、多くの場合、チームのSQLに対する習熟度、マルチデータベースサポートの必要性、そして明示的なSQLと高レベルの抽象化レイヤーのどちらを好むかによって決まります。

  • Flywayを選ぶべき場合:
    • プロジェクトが主に単一のデータベースタイプを使用している場合。
    • チームが生のSQLの記述とレビューに慣れている場合。
    • マイグレーションスクリプトのシンプルさと透明性を好む場合。
    • ロールバックを手動で処理すること、またはDDL変更に対して「フォワードオンリー」のマイグレーション戦略を採用することに抵抗がない場合。
  • Liquibaseを選ぶべき場合:
    • 単一のコードベースで複数のデータベースタイプをサポートする必要がある場合。
    • 堅牢な自動ロールバック機能を重視する場合。
    • チームがスキーマ変更に対してより宣言的なアプローチを好む場合(特に一部の開発者が特定のSQL方言に経験が浅い場合)。
    • コンテキスト、ラベル、複雑なリファクタリングタイプなどの高度な機能が必要な場合。

選択が完了したら、次の論理的なステップは統合です。ほとんどの最新アプリケーションは、これらのツールをビルドプロセス(Javaプロジェクトの場合はMavenやGradleなど)に直接組み込むか、CI/CDパイプライン内で専用のコマンドラインステップとして実行します。Javaで構築されていないアプリケーションでも、両方のツールは、事実上すべてのスクリプト言語から呼び出すことができるコマンドラインクライアントを提供します。

実装ガイド:マイグレーションを実践する

両方のツールの基本的な実践例を見ていきましょう。一般的な統合ポイントであるため、JavaとMavenを使用してデモンストレーションを行いますが、コアコンセプトはアプリケーションスタックに関係なく適用されることを常に忘れないでください。

Flywayの例:シンプルなSQLマイグレーション

1. プロジェクトにFlywayを追加する(Maven pom.xml):

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>9.16.0</version> <!-- 最新の安定バージョンを使用 -->
</dependency>

2. Flywayを構成する(例:application.propertiesまたは類似の設定ファイル):

Flywayはプログラムで、またはプロパティファイル経由で構成できます。Spring Bootを使用している場合、通常は最小限の労力で自動構成されます。基本的なプログラム設定例を次に示します。

// 例:Javaアプリケーションでのプログラムによる設定
import org.flywaydb.core.Flyway;
import javax.sql.DataSource;

public class DatabaseMigrator {
    public static void migrate(DataSource dataSource) {
        Flyway flyway = Flyway.configure()
            .dataSource(dataSource)
            .locations("classpath:db/migration") // SQLマイグレーションスクリプトの場所を指定
            .load();
        flyway.migrate();
    }
}

3. 最初のマイグレーションスクリプトを作成する(src/main/resources/db/migration/V1__Create_initial_tables.sql):

Flywayは、マイグレーションスクリプトが厳密な命名規則に従うことを要求します。V<バージョン>__<説明>.sql。バージョンセグメントは、11.12.0.1などの数字とドットで構成できます。

-- V1__Create_initial_tables.sql
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    description TEXT
);

4. 2番目のマイグレーションスクリプトを作成する(src/main/resources/db/migration/V2__Add_products_category.sql):

-- V2__Add_products_category.sql
ALTER TABLE products
ADD COLUMN category VARCHAR(50) DEFAULT 'Uncategorized';

UPDATE products
SET category = 'Electronics'
WHERE id = 1; -- 例:特定の製品のカテゴリを更新

5. マイグレーションを実行する:

アプリケーションが起動し、DatabaseMigrator.migrate(dataSource)が呼び出されたとき、またはmvn flyway:migrate(Mavenプラグインを使用している場合)を実行したとき、Flywayは次の手順を実行します。

  • データベース内のflyway_schema_historyテーブル(または構成したカスタム名)をチェックします。
  • まだ適用されていない保留中のマイグレーション(例:V1、V2)を特定します。
  • これらのマイグレーションを正しいバージョン順に順次実行します。
  • 最後に、履歴テーブルにその成功した実行を記録します。
# FlywayコマンドラインツールまたはMavenプラグインを使用する例
# Mavenを使用している場合:
mvn flyway:migrate

# コマンドラインクライアントを使用している場合、構成後に
flyway migrate

これらのコマンドを実行すると、productsテーブルには新しいcategoryカラムが追加され、id=1の製品のカテゴリは明示的に更新されます。Flywayは内部的にflyway_schema_historyテーブルを作成し、適用されたすべてのマイグレーションとそのステータスを綿密に追跡します。

Liquibaseの例:抽象化されたチェンジセット

1. プロジェクトにLiquibaseを追加する(Maven pom.xml):

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>4.27.0</version> <!-- 最新の安定バージョンを使用 -->
</dependency>

2. マスタチェンジログファイルを作成する(src/main/resources/db/changelog/db.changelog-master.yaml):

このマスターファイルは、Liquibaseの主要なエントリーポイントとして機能し、他の個々のチェンジログファイルを調整して含めます。

# db.changelog-master.yaml
databaseChangeLog:
  - include:
      file: db/changelog/001-initial-schema.yaml
  - include:
      file: db/changelog/002-add-product-category.yaml

3. 最初のチェンジログを作成する(src/main/resources/db/changelog/001-initial-schema.yaml):

# 001-initial-schema.yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: your_name
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: INT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: username
                  type: VARCHAR(50)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: email
                  type: VARCHAR(100)
                  constraints:
                    nullable: false
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueComputed: CURRENT_TIMESTAMP
        - createTable:
            tableName: products
            columns:
              - column:
                  name: id
                  type: INT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: name
                  type: VARCHAR(100)
                  constraints:
                    nullable: false
              - column:
                  name: price
                  type: DECIMAL(10, 2)
                  constraints:
                    nullable: false
              - column:
                  name: description
                  type: TEXT

4. 2番目のチェンジログを作成する(src/main/resources/db/changelog/002-add-product-category.yaml):

# 002-add-product-category.yaml
databaseChangeLog:
  - changeSet:
      id: 2
      author: your_name
      changes:
        - addColumn:
            tableName: products
            columns:
              - column:
                  name: category
                  type: VARCHAR(50)
                  defaultValue: 'Uncategorized'
        - update:
            tableName: products
            set:
              category: 'Electronics'
            where: "id = 1"

5. マイグレーションを実行する:

Flywayと同様に、Liquibaseはプログラムで、Maven/Gradleプラグインを介して、またはコマンドラインクライアントから直接実行できます。

// 例:Javaアプリケーションでのプログラムによる設定
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
import javax.sql.DataSource;
import java.sql.Connection;

public class DatabaseMigrator {
    public static void migrate(DataSource dataSource) throws Exception {
        try (Connection connection = dataSource.getConnection()) {
            Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
            Liquibase liquibase = new Liquibase("db/changelog/db.changelog-master.yaml", new ClassLoaderResourceAccessor(), database);
            liquibase.update(""); // コンテキストが空の場合、すべてのチェンジセットが適用される
        }
    }
}
# LiquibaseコマンドラインツールまたはMavenプラグインを使用する例
# Mavenを使用している場合:
mvn liquibase:update

# コマンドラインクライアントを使用している場合、構成後に
liquibase update

Liquibaseが実行されると、データベースに2つの重要なテーブルが作成されます。DATABASECHANGELOG(Flywayの履歴テーブルと同様の機能を果たす)とDATABASECHANGELOGLOCK(同時実行マイグレーションの管理と競合の防止に不可欠)です。

データ処理に関する個人的なメモ

データベースマイグレーションを扱う際には、データの移動や変換がしばしば伴います。メジャースキーマ変更を適用する前に、データをステージングしたり、レガシー情報をインポートしたり、単にデータ形式を変換したりする必要があったことは数えきれません。

例えば、データインポートのためにCSVをJSONに素早く変換する必要がある場合、私はよくtoolcraft.app/ja/tools/data/csv-to-jsonを使用します。これは完全にブラウザ内で動作するため、機密データが私のマシンを離れることはなく、クライアント情報を扱う際に非常に安心できます。これは小さなユーティリティですが、マイグレーションスクリプトや初期データベースのシード作業のためにデータを準備する際に、かなりの時間と手間を省いてくれました。

結論:自動データベースマイグレーションの採用

自動データベースマイグレーションツールは、現代のソフトウェア開発において真に不可欠です。これらはスキーマ変更を標準化し、エラーの可能性を劇的に減らし、開発チーム内でのよりスムーズなコラボレーションを促進します。

FlywayのSQL中心のシンプルさを好むか、Liquibaseの強力な抽象化と堅牢なロールバック機能を好むかにかかわらず、これらのツールのいずれかを採用することは、データベース管理ワークフローを大幅に強化するでしょう。小規模から始め、既存のビルドプロセスに慎重に統合し、徐々にその全機能を活用してください。未来のあなたとあなたのデータベースは、間違いなく感謝するでしょう。

Share: