電通総研 テックブログ

電通総研が運営する技術ブログ

mysqldump で Aurora Serverless v1 から v2 にデータ移行した

こんにちは。コーポレート本部 サイバーセキュリティ推進部の耿です。

大好きだった Aurora Serverless v1 が 2024/12/31 をもってサポート終了ということで、泣く泣く Aurora Serverless v2 へ移行しました。公式ではクラスタのアップグレードを使用した手順が紹介されていますが、今回はそれではなく Aurora Serverless v2 クラスタを新規に作成し、mysqldump によるデータ移行という方法を取りました。その概要を書き残します。

Aurora Serverless v1 の好きなところ

正直なところ v1 を使い続けたいと思っていました。

  • v1はv2よりも「サーバレス」という言葉がよく似合う
    • インスタンスや AZ を意識する必要がなく、AZ 障害時に自動でフェイルオーバーする
    • アクティビティがしばらくない場合は一時停止できる。特に開発環境などにおいて費用の節約になる
  • v2よりも安い
    • 機能面の違いがあるとはいえ、同じキャパシティではv2の半分の料金である
  • Data API、クエリエディタが便利

しかしサポート終了には対応しなければならないので、いい感じの移行の方法を検討しました。

なぜ mysqldump を使ったのか

公式の移行ガイドではクラスタのアップグレードを利用する方法が紹介されていましたが、今回の環境で適用するには難点があると判断しました。一番の理由は CDK を利用してリソース管理していたためです。

まず、CDK/CloudFormationによるデプロイでは既存クラスタのアップグレードを実施できない(DB作り直し扱いになる)ため、手動でアップグレードを実施する必要があります。手動でDBクラスタをアップグレードすると、CDKコードの状態と実際のDBクラスタの状態に差分が生じてしまいます。この状態はIaCでインフラリソースを管理している以上避けたいです。

cdk importcdk migrateを使って、実際のリソースをCDK管理下に置くことはできます。しかし今回はAuroraクラスタのパスワードローテーションもCDKで実装しており、DB移行後も同様にCDKコード上でそれを表現したいです。調査した限りでは、Auroraクラスタのパスワードを管理しているSecrets Managerシークレットや、ローテーションを実行するLambda関数も含めてきれいにCDKにimport/migrateする方法は見つかりませんでした。

逆に考えると、パスワードローテーション関連のリソースも含めてCDKできれいに管理したければ、シンプルに新しいServerless v2クラスタを新規にCDKから作成すれば良いということになります。今回の環境ではある程度システム停止時間を設けられるということもあり、静止点を設けてv1からv2へのデータ移行する方針で進めることにしました。

MySQLを利用していたので、mysqldumpを使うことにしました。

移行手順

作業概要

1. 事前準備:CDKで Serverless v2 クラスタを作成

従来使っていた Serverless v1 クラスタはこんな感じで構築していました。

const auroraSubnetGroup = new rds.SubnetGroup(this, "SubnetGroup", {
    vpc: vpc,
    vpcSubnets: { subnetGroupName: "dbSubnet" },
});

const parameterGroup = new rds.ParameterGroup(this, "ParameterGroup", {
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    parameters: {
        character_set_client: "utf8mb4",
        character_set_connection: "utf8mb4",
        character_set_database: "utf8mb4",
        character_set_results: "utf8mb4",
        character_set_server: "utf8mb4",
        time_zone: "Asia/Tokyo",
    },
});

// Serverless v1 クラスター
const myCluster = new rds.ServerlessCluster(this, "MyCluster", {
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    clusterIdentifier: "my-cluster",
    defaultDatabaseName: "db-name",
    parameterGroup: parameterGroup,
    removalPolicy: RemovalPolicy.RETAIN,
    deletionProtection: true,
    vpc: vpc,
    vpcSubnets: { subnetGroupName: "dbSubnet" },
    scaling: { minCapacity: 1, maxCapacity: 2, autoPause: Duration.hours(0) },
    securityGroups: [dbSecurityGroup],
    subnetGroup: auroraSubnetGroup,
    enableDataApi: true,
    backupRetention: Duration.days(7),
});

// マスターユーザーのシングルユーザーローテーション
myCluster.addRotationSingleUser({
    vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

// アプリケーションが使うユーザーのマルチユーザーローテーション
const appSecret = new rds.DatabaseSecret(this, "AppSecret", {
    username: "app",
    secretName: "AppSecret",
    masterSecret: myCluster.secret,
});

const appSecretAttached = appSecret.attach(myCluster);
myCluster.addRotationMultiUser("AppUserRotation", {
    secret: appSecretAttached,
    vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

新規に Serverless v2 クラスタを次のように作ります。

const parameterGroupV2 = new rds.ParameterGroup(this, "ParameterGroupV2", {
    engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_3_06_0 }),
    parameters: {
        character_set_client: "utf8mb4",
        character_set_connection: "utf8mb4",
        character_set_database: "utf8mb4",
        character_set_results: "utf8mb4",
        character_set_server: "utf8mb4",
        time_zone: "Asia/Tokyo",
    },
});

// Serverless v2 クラスター
const myClusterV2 = new rds.DatabaseCluster(this, "MyClusterV2", {
    clusterIdentifier: "my-cluster-v2",
    engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_3_06_0 }),
    vpc: vpc,
    parameterGroup: parameterGroupV2,
    vpcSubnets: { subnetGroupName: "dbSubnet" },
    defaultDatabaseName: "db-name",
    removalPolicy: RemovalPolicy.RETAIN,
    deletionProtection: true,
    securityGroups: [dbSecurityGroup],
    subnetGroup: auroraSubnetGroup,
    // ここではライターインスタンス1つのみの構成
    writer: rds.ClusterInstance.serverlessV2("Writer", {}),
    serverlessV2MinCapacity: 0.5,
    serverlessV2MaxCapacity: 2.0,
    storageEncrypted: true,
    backup: { retention: Duration.days(7) },
    // バックトラック機能を有効
    backtrackWindow: Duration.days(3),
});

// マスターユーザーのシングルユーザーローテーション
myClusterV2.addRotationSingleUser({
    vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

// アプリケーションが使うユーザーのマルチユーザーローテーション
const appSecretV2 = new rds.DatabaseSecret(this, "AppSecretV2", {
    username: "app",
    secretName: "AppSecret",
    masterSecret: myClusterV2.secret,
});

const appSecretAttachedV2 = appSecretV2.attach(myClusterV2);
myClusterV2.addRotationMultiUser("AppUserRotationV2", {
    secret: appSecretAttachedV2,
    vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

シークレットローテーションの設定はv1と全く同じです。
Serverless v1ではサポートされていなかったバックトラック機能は便利なので有効にしてあります。

2. 事前準備:旧DBのテーブル定義の確認

v2のMySQLではまだクエリエディタがサポートされていないので、踏み台インスタンスを作成して接続確認をします。
v1の各テーブルの定義を確認しておきます。

SHOW FULL COLUMNS FROM <テーブル名>;

3. データ移行

データ移行本番ではアプリケーションからDBへの接続を停止し、移行の成功を確認するために各テーブルのデータ行数を確認しました。

SELECT COUNT(*) FROM <テーブル名>;

そして旧DBのデータダンプを取ります。

mysqldump -u admin -p -h <HOST_NAME_V1> <DB_NAME> --set-gtid-purged=OFF > db_dump.sql

グローバルトランザクションIDに関する情報を出力するとデータロード時にエラーが発生しました。グローバルトランザクションIDの変更は不要なので --set-gtid-purged=OFF オプションを付けています。

今回は MySQL 5.7 から 8.0 への移行ですが、8.0ではデフォルトの照合順序がutf8mb4_0900_ai_ciに変わっています。旧DBと同じ設定になるよう、ダンプファイル内の DEFAULT CHARSETutf8mb4_general_ci に明示的に変えておきます。

sed 's/DEFAULT CHARSET=utf8mb4/DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci/' db_dump.sql > db_dump_modified.sql

新DBにデータをロードします。

mysql -u admin -p -h <HOST_NAME_V2> <DB_NAME> < db_dump_modified.sql

ロードが完了したら、新DBの各テーブルの定義とデータ行数を確認します。

SHOW FULL COLUMNS FROM <テーブル名>;
SELECT COUNT(*) FROM <テーブル名>;

4. データ移行後作業

データ移行後は以下の作業を行いました。

  • マスターユーザーでDBに接続し、アプリが利用するDBユーザーを作成(パスワードはSecrets Managerに作成されたシークレットから確認)
  • 手動でSecrets Managerのパスワードローテーションを実行し、成功することを確認
  • アプリケーションが新DBを使用するように変更
  • アプリケーションの接続確認
  • 踏み台インスタンス上のデータダンプを削除
  • しばらく運用し、問題なければAurora Serverless v1クラスターと関連リソースを削除

これで無事に Aurora Serverless v2 への移行が完了しました。

さいごに

公式の移行ガイドとは違う方法でしたが無事にデータ移行が完了し、CDKコードもきれいな状態で満足です。
踏み台インスタンスを運用したくないので、早くMySQLもData APIをサポートしないかなぁと首を長くして待っています。

さよなら、ありがとう、Aurora Serverless v1

執筆:@kou.kinyo、レビュー:Ishizawa Kento (@kent)
Shodoで執筆されました