RDS for MySQLからAuroraへの移行ガイド

2ヶ月くらい前に、RDS for MySQLからAurora MySQLへDB移行したので、そのときに検討したことや実際の作業手順などをまとめました。

同様の作業をされる方の参考になれば幸いです。

## 背景
コストをケチって、サービスのリリース後もシングルAZのRDS for MySQLを利用していましたが(よくない)、クライアントやトラフィックが日々増えていくなかで、データの耐久性と将来的なパフォーマンスに不安がでてきていました。そこで、複数AZにデータを分散し、MySQLのパフォーマンスが最大5倍向上するAuroraへ移行することにしました。

## 移行
- 新規で作成するリソース(Auroraクラスター、インスタンス、サブネットグループ、パラメータグループ)についてはTerraformでコード管理することにしました(移行元のRDSはコード管理していなかった)
- アプリケーション(Lambda)との接続はRDS Proxyのターゲット先を変更することで完了しました
- 移行手段としてスナップショットかDMSを検討しましたが、ダウンタイムを前提としてスナップショットを選択しました。これによってRDSインスタンスで使用されていたユーザー名とパスワードがそのまま引き継がれます
- Auroraのストレージレイヤーは選択したDBサブネットグループ内のAZに関係なく、自動的にデータを複数のAZに分散してレプリケーションします

## Aurora vs Aurora Serverless
将来的なトランザクションレートとデータ量の増加を考慮し、Aurora ServerlessよりもAuroraが適していると判断しました。データの3AZに渡るレプリケーションは無料で提供され、追加のコストは使用するストレージ量に依存します。

## コスト見積
- 初期設定
    - 一旦、レプリカは使用しない
- 料金体系
    - RDS: インスタンスの起動時間 + ストレージの容量に応じた料金
    - Aurora: インスタンスの起動時間 + ストレージの容量に応じた料金 ＋ I/Oリクエスト数
- 費用試算
    - インスタンス: `db.r6g.xlarge`にすると約0.1 USD  × 24 × 30 = 約72 USD/月増
    - ストレージ: 変更なし
    - I/Oリクエスト: 秒間200回のリクエストで、約124 USD/月の増加(0.24　USD/100万リクエスト)
        - AWSサポートによると、RDSのI/OからAuroraのI/Oは見積もれないとのこと。そのため、試算はあくまで参考
            
            > RDS の I/O に関する実績値を元に Aurora の I/O 料金を見積もる方法についてお問い合わせいただいております。
            恐れ入りますが、RDS と Aurora では I/O の仕組みが異なっており、RDS の I/O から Aurora の I/O を見積もることはできません。RDS では、ログレコードやデータページなどの書き込みで I/O が発生いたしますが、Aurora の場合はログレコードのみとなり、I/O の考え方自体が異なるためでございます。[1]
            > 

## 検討事項
- インスタンスの選択: 移行元の`db.t3.xlarge`と近いスペックの`db.r6g.xlarge`を初期選定として採用しました
- インスタンスタイプの選択: 高I/Oが見込まれるのでI/O最適化インスタンスを使用し、日毎のI/Oコストが全体の25 %以下だった場合にはスタンダードインスタンスに切り替える方針です
- Aurora Serverlessの検討: まずはAuroraを採用し、Serverlessの方が適していそうであれば途中で切り替えを検討します
- バージョン互換性の問題: 移行元RDSのMySQLバージョンが8.0.36で、2024/5時点では互換のAuroraエンジンがありませんでした。mysqldumpから論理移行する方法も検討しましたが、互換エンジンのリリースを待つことにしました
    ⇒ 2024/6/4にAurora MySQLデータベースエンジン3.0.7(MySQL 8.0.36互換)が[リリース](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.30Updates.html)されたので、こちらを使用することにしました

## 移行手順
以下が移行の大まかな流れです。

1. サービスの一時停止
2. 移行元RDSのスナップショット`database-prod-snapshot`作成
3. Terraformの適用
4. RDS Proxyの接続先をAuroraに変更
5. サービス再開
6. 動作確認
7. 動作確認で問題なければ、移行元RDSを停止(または削除)

### 移行元RDSのスナップショット`database-prod-snapshot`作成
サービスが停止している間に、移行元のRDSインスタンスから安全にスナップショットを取得します。これにより、データの一貫性が保たれ、移行中のデータ損失を防ぎます。

![スクリーンショット 2024-08-10 11.26.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/38e35850-a7d5-f473-fb50-026a1e95a5fd.png)

### Terraformの適用
以下のTerraformコードは、Auroraクラスター、Auroraインスタンス、クラスターパラメータグループ、インスタンスパラメータグループ、およびサブネットグループの作成を自動化します。こちらは本番環境用の設定となります。

```hcl
resource "aws_rds_cluster" "aurora_cluster_prod" {
  cluster_identifier                  = "aurora-cluster-prod"  # クラスタの識別子
  engine                              = "aurora-mysql"         # 使用するデータベースエンジン
  engine_version                      = "8.0.mysql_aurora.3.07.0"  # データベースエンジンのバージョン
  db_subnet_group_name                = aws_db_subnet_group.aurora_db_subnet_group_prod.name  # DBサブネットグループの指定
  skip_final_snapshot                 = false                  # 削除時の最終スナップショットのスキップを無効化
  snapshot_identifier                 = "arn:aws:rds:ap-northeast-1:************:snapshot:database-prod-snapshot"  # スナップショットのARN
  vpc_security_group_ids              = [var.security_group_rds_sg_id]  # セキュリティグループIDのリスト
  db_cluster_parameter_group_name     = aws_rds_cluster_parameter_group.aurora_cluster_parameter_group_prod.name  # DBクラスタパラメータグループ
  deletion_protection                 = true                   # 削除保護の有効化
  backup_retention_period             = 7                      # バックアップの保持期間（日）
  preferred_maintenance_window        = "sat:08:00-sat:12:00"  # 優先メンテナンスウィンドウ(UTC)
  preferred_backup_window             = "13:00-19:00"          # バックアップ実行の優先時間帯(UTC)
  copy_tags_to_snapshot               = true                   # スナップショットへのタグのコピーを有効化
  enabled_cloudwatch_logs_exports     = ["audit", "error", "general", "slowquery"]  # CloudWatchにエクスポートするログタイプ
  storage_type                        = "aurora-iopt1"         # ストレージタイプ

  tags = {
    Environment = "prod"
  }
}

resource "aws_rds_cluster_instance" "aurora_cluster_instance_prod" {
  identifier                   = "aurora-instance-prod-1"  # インスタンス識別子
  cluster_identifier           = aws_rds_cluster.aurora_cluster_prod.id  # クラスタ識別子
  instance_class               = "db.r6g.xlarge"           # インスタンスクラス
  engine                       = "aurora-mysql"            # エンジンタイプ
  engine_version               = "8.0.mysql_aurora.3.07.0" # エンジンバージョン
  availability_zone            = "ap-northeast-1a"         # 利用するAZ
  db_parameter_group_name      = aws_db_parameter_group.aurora_instance_parameter_group_prod.name  # DBパラメータグループ
  ca_cert_identifier           = "rds-ca-rsa4096-g1"       # SSL証明書の識別子
  auto_minor_version_upgrade   = true                      # マイナーバージョンの自動アップグレードを有効化
  performance_insights_enabled = true                      # パフォーマンスインサイトを有効化
  monitoring_interval          = 60                        # モニタリング間隔（秒）
  monitoring_role_arn          = "arn:aws:iam::************:role/rds-monitoring-role"  # モニタリング用IAMロールのARN

  tags = {
    Environment = "prod"
  }
}

resource "aws_rds_cluster_parameter_group" "aurora_cluster_parameter_group_prod" {
  name        = "aurora-cluster-parameter-group-prod"  # パラメータグループ名
  family      = "aurora-mysql8.0"  # パラメータグループのファミリ
  description = "Aurora MySQL Cluster Parameter Group for prod"  # パラメータグループの説明

  # パラメータ設定は省略してデフォルト設定を使用
}

resource "aws_db_parameter_group" "aurora_instance_parameter_group_prod" {
  name        = "aurora-instance-parameter-group-prod"  # パラメータグループ名
  family      = "aurora-mysql8.0"  # パラメータグループのファミリ
  description = "Aurora MySQL Instance Parameter Group for prod"  # パラメータグループの説明

  # パラメータ設定は省略してデフォルト設定を使用
}

resource "aws_db_subnet_group" "aurora_db_subnet_group_prod" {
  name        = "aurora-db-subnet-group-prod"  # サブネットグループ名
  description = "Subnet group for Aurora MySQL cluster in production environment"  # サブネットグループの説明
  subnet_ids  = [var.subnet_db_private_1a_id, var.subnet_db_private_1c_id]  # サブネットID

  tags = {
    Environment = "prod"
  }
}
```

#### 補足(開発用のクラスターとインスタンス)
開発用のAuroraクラスターとインスタンスの設定は本番環境のものと類似していますが、コスト削減と柔軟性の観点から以下のような変更を加えています。
- ログ出力: CloudWatchへのログ出力設定(`enabled_cloudwatch_logs_exports`)を省略しています。これにより、開発環境でのログ記録コストを抑えることができます
- ストレージタイプ: `storage_type`の指定を省略しており、デフォルトのスタンダードインスタンスが使用されます。開発環境ではI/Oが少ないため、これでコストを最適化できます
- 削除保護: `deletion_protection`を無効化しています。開発中にクラスタを迅速に再デプロイまたは削除することができます
- インスタンスクラス: 低コストのインスタンスクラス(`instance_class`)を選択しています。開発環境での運用コストを削減しつつ、必要十分なパフォーマンスを確保しています
- パフォーマンスインサイト: `performance_insights_enabled`を無効化しています。開発環境ではパフォーマンス監視の詳細な分析が不要な場合が多いため、コスト削減のために無効化します
- 拡張モニタリング: `monitoring_role_arn`と`monitoring_interval`の設定を省略し、拡張モニタリングを無効化しています。開発環境の運用コストをさらに削減します

```hcl
resource "aws_rds_cluster" "aurora_cluster_dev" {
  cluster_identifier                  = "aurora-cluster-dev"
  engine                              = "aurora-mysql"
  engine_version                      = "8.0.mysql_aurora.3.07.0"
  db_subnet_group_name                = aws_db_subnet_group.aurora_db_subnet_group_dev.name
  skip_final_snapshot                 = false
  snapshot_identifier                 = "arn:aws:rds:ap-northeast-1:************:snapshot:database-dev-snapshot"
  vpc_security_group_ids              = [var.security_group_rds_sg_id]
  db_cluster_parameter_group_name     = aws_rds_cluster_parameter_group.aurora_cluster_parameter_group_dev.name
  deletion_protection                 = false
  backup_retention_period             = 7
  preferred_maintenance_window        = "sat:08:00-sat:12:00"
  preferred_backup_window             = "13:00-19:00"
  copy_tags_to_snapshot               = true

  tags = {
    Environment = "dev"
  }
}

resource "aws_rds_cluster_instance" "aurora_cluster_instance_dev" {
  identifier                   = "aurora-instance-dev-1"
  cluster_identifier           = aws_rds_cluster.aurora_cluster_dev.id
  instance_class               = "db.t3.medium"
  engine                       = "aurora-mysql"
  engine_version               = "8.0.mysql_aurora.3.07.0"
  availability_zone            = "ap-northeast-1a"
  db_parameter_group_name      = aws_db_parameter_group.aurora_instance_parameter_group_dev.name
  ca_cert_identifier           = "rds-ca-rsa4096-g1"
  auto_minor_version_upgrade   = true
  performance_insights_enabled = false

  tags = {
    Environment = "dev"
  }
}
```

### RDS Proxyの接続先をAuroraに変更
RDS Proxyの詳細画面からターゲットグループセクションをみつけて、編集ボタンを押すと以下のページに飛びます。データベースを今回作成したAuroraクラスター`aurora-cluster-prod`に設定して保存することで、接続先が変更されます。

![スクリーンショット 2024-08-10 13.24.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/d01a1722-93b8-d6ef-281a-8c9ec276647c.png)

## 反省点
### 移行時のコスト
具体的な料金は書きませんが、DB移行日のAuroraコストが想定以上だったので焦りました。
スナップショットからインスタンスを作成する過程で大量の書き込みが発生したので、その分のI/Oコストが発生したのだと思います。この点についても移行前に考慮すべきでした。
![スクリーンショット 2024-08-10 13.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/d52a1ca7-f87a-e0cc-392c-058c8bdcdd7a.png)

### セッションタイムアウト
GitHub Actionsから`terraform apply`を実行したところ、AssumeRoleするIAMロールの最大セッション時間が短くてタイムアウトしてしまいました。結局、日をあらためて再リリースすることになってしまいました。

https://qiita.com/suzuki0430/items/8b4f05e1a72aef70a5d3

### RDS停止後の自動再起動
移行元のRDSを一旦停止したのですが、7日後に自動再起動してしまい、余計なコストが発生してしまいました。移行後のAuroraに問題がないことを確認できた時点で、すぐにRDSを削除すべきでした。

## 参考

https://aws.amazon.com/jp/rds/instance-types/

https://aws.amazon.com/jp/rds/mysql/pricing/?pg=pr&loc=2

https://aws.amazon.com/jp/rds/aurora/pricing/

https://www.stylez.co.jp/aws_columns/learn_about_aws_managed_databases/aurora_or_non-aurora_rds/#AuroraAuroraRDS

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Migrating.RDSMySQL.Snapshot.html#AuroraMySQL.Migrating.RDSMySQL.Space