LoginSignup
1
0

More than 3 years have passed since last update.

gh-ost実行履歴をflywayのバージョン管理下に置くにはどうするか

Last updated at Posted at 2020-03-23

gh-ostとflywayについて

  • gh-ost : オンラインスキーマ変更ツール
  • flyway : DBマイグレーションツール

なぜgh-ostを使うのか/なぜflywayを使うのかは公式に任せる

解決したい問題

flywayでバージョン管理したいが、gh-ostを直接実行してスキーマ変更すると、その変更はflyway管理下に置けない
flyway migrate したときにgh-ostコマンドが実行されて、成功/失敗がマークされるようにしたい

どうするのか

flywayの Custom Migration resolvers & executors を使って gh-ost コマンドを実行できるようにする
公式は使い方が全く書いてないので、以下使い方を解説する
なお、この記事では flyway-core 6.1.1を使用
OSコマンドを実行することになるので自己責任で

Custom Migration resolvers & executors について

登場人物

  • MigrationExecutor
    • flyway migrateしたときの処理を定義するためのクラス
    • ResolvedMigration に持たせることで、Flyway本体に実行させることができる
    • executeを実装すればよい
  • ResolvedMigration
    • 1つのmigrationのversionやdescription、 MigrationExecutor などを定義するためのクラス
    • getVersion など実装すべきメソッドは多いが、いちばん大事なのは getExecutor
    • flyway infoしたときに表示される表の1行分の情報を持つと思えばいい。flyway infoの表ってのは↓みたいなやつ。
+-----------+---------+------------------------------------------+------+--------------+---------+
| Category  | Version | Description                              | Type | Installed On | State   |
+-----------+---------+------------------------------------------+------+--------------+---------+
| Versioned | 1.0     | init                                     | SQL  |              | Pending |
| Versioned | 1.1     | add index for hoge                       | SQL  |              | Pending |
| Versioned | 1.2     | renamed unnecessary tables               | SQL  |              | Pending |
| Versioned | 2.0     | drop unnecessary tables                  | SQL  |              | Pending |
+-----------+---------+------------------------------------------+------+--------------+---------+
  • MigrationResolver
    • ファイルなどを解決して Collection<ResolvedMigration> を返す
    • デフォではSQL-based MigrationsとJava-based Migrationsに必要なResolverが定義されている
    • resolveMigrations を実装すればよい

実装

src/main/resources/ghost に置いたファイルを読み込んでgh-ost使っていくケースを書いてみる

概要

  1. Maven PluginGradle Plugin あたりを使ってflywayを実行できるようにしておく
  2. MigrationExecutorを実装したクラスを作成
  3. ResolvedMigrationを実装したクラスを作成
  4. MigrationResolverを実装したクラスを作成
  5. Resolverをconfで指定する

1. Maven PluginGradle Plugin あたりを使ってflywayを実行できるようにしておく

とりあえず、ここではMaven Pluginの例を。別にMavenじゃなくてもいいです。

pom.xml
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>6.1.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>6.1.1</version>
                <configuration>
                    <locations>
                        <location>classpath:db/migration</location>
                    </locations>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.18</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

2. MigrationExecutorを実装したクラスを作成

class GhostMigrationExecutor implements MigrationExecutor {

    private final File file;

    GhostMigrationExecutor(File file) {
        this.file = file;
    }

    @Override
    public void execute(Context context) throws SQLException {
        // fileを読み込んで、ここで具体的なgh-ostのコマンドを実行する。ProcessBuilderなどを使うことになると思う。
    }

    @Override
    public boolean canExecuteInTransaction() {
        return false; //gh-ostはALTER TABLEしか扱えなくて、それはオートコミットなのでトランザクションに入れたらダメ。
    }
}

3. ResolvedMigrationを実装したクラスを作成

class ResolvedGhostMigration implements ResolvedMigration {

    private final File file;
    private final MigrationVersion version;
    private final String description;

    ResolvedGhostMigration(File file) {
        //この辺はFlywayオリジナルのBaseJavaMigrationがクラス名からversionとdescriptionを割り出すロジックを流用してる
        boolean repeatable = file.getName().startsWith("R");
        if (!file.getName().startsWith("V") && !repeatable) {
            throw new FlywayException("Invalid file name: " + file.getName() + " => ensure it starts with V or R");
        } else {
            String prefix = file.getName().substring(0, 1);
            Pair<MigrationVersion, String> info =
                    MigrationInfoHelper.extractVersionAndDescription(file.getName(), prefix, "__", new String[]{".properties"}, repeatable);
            this.file = file;
            this.version = info.getLeft();
            this.description = info.getRight();
        }
    }

    @Override
    public MigrationVersion getVersion() {
        return this.version;
    }

    @Override
    public String getDescription() {
        return this.description;
    }

    @Override
    public String getScript() {
        return this.file.getName();
    }

    @Override
    public Integer getChecksum() {
        // こいつがnullを返すのはオリジナルのResolvedJavaMigrationに準ずる。コンストラクタで渡されたfileから一意なchecksumを作ってnull以外を返してもよい。
        // checksumを設定しておくと、すでにSuccessになったmigrationに利用したファイルをあとから編集したときにエラーを吐いてくれるようになる。
        return null;
    }

    @Override
    public MigrationType getType() {
        // flyway infoしたときの `Type` カラムの値に使われる。Custom MigrationであることがわかるようにCUSTOMを返す
        return MigrationType.CUSTOM;
    }

    @Override
    public String getPhysicalLocation() {
        return this.file.getAbsolutePath();
    }

    @Override
    public MigrationExecutor getExecutor() {
        return new GhostMigrationExecutor(this.file); //2で作成したExecutorを返すようにする
    }
}

4. MigrationResolverを実装したクラスを作成

public class GhostMigrationResolver implements MigrationResolver {
    @Override
    public Collection<ResolvedMigration> resolveMigrations(Context context) {
        try {
            return Files.walk(Paths.get(getClass().getResource("/ghost/").toURI())) //src/main/resources/ghostディレクトリを読み込み
                    .map(Path::toFile)
                    .filter((file) -> !file.isDirectory()) // ghostディレクトリ自身を除外
                    .map(file -> new ResolvedGhostMigration(file)) // 3で作ったResolvedMigrationのインスタンスを生成
                    .collect(Collectors.toList());
        } catch (IOException | URISyntaxException e) {
            throw new FlywayException(e.getMessage(), e.getCause());
        }
    }
}

5. Resolverをconfで指定する

Mavenで作ったので、pom.xmlで指定できる

pom.xml
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>6.1.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>6.1.1</version>
                <configuration>
                    <locations>
                        <location>classpath:db/migration</location>
                    </locations>
                    <!-- ここから -->
                    <resolvers>
                        <resolver>path.to.GhostMigrationResolver</resolver>
                    </resolvers>
                    <!-- ここまで追加 -->
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.18</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

もしくは、flyway.confで指定してもよい

# ...その他の設定
flyway.resolvers=path.to.GhostMigrationResolver

注意

gh-ostとflywayは別々にコネクションを獲得することになり、gh-ostの実行中にflywayのコネクションがタイムアウトする可能性がある
実行する際は autoReconnect=true にするなどの対策をしておいたほうがいい

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0