DMSでalloyDB for PostgreSQLのバージョンをアップデート

## はじめに

AlloyDBがGA（General Availability: 一般提供）されてから数年が経ちました。AlloyDB for PostgreSQLがリリースされた当初のPostgreSQLはバージョン14でしたが、今では16になっています。AlloyDBがリリースされた当初から使用している私も、刻一刻と近づくサポート終了を意識せざるを得なくなってきました。そこで今回はTerraformを駆使してAlloyDBのアップデートについて調べた内容を記録します。この記事は私自身の備忘録として記録したものであり、効率的かつ計画的なアップデートのための基礎情報を整理した内容です。ただし、内容にはまだ間違いや不足が含まれている可能性があり、今後の学びや発見に応じて更新していく予定です。

### AlloyDB for PostgreSQLのバージョンアップデート概要

AlloyDBのバージョンアップデートは、画像のようにプレビュー版の機能ではあるものの、AlloyDBコンソール上で簡単にアップグレードすることが可能です。

[公式ドキュメント](https://cloud.google.com/alloydb/docs/major-version-upgrade-inplace-overview)によると、ダウンタイムが20分から1時間程度発生することが予想されています。ダウンタイムは短ければ短いほどサービスにとっては好ましいものです。

そこで今回はDatabase Migration Serviceを使ったAlloyDBのバージョンアップデートを行い、ダウンタイムを短縮する方法を考えました。Database Migration Serviceは、PostgreSQL互換のAlloyDBに対して継続的なデータレプリケーションを実現できるため、短いダウンタイムでの移行が可能となる点で非常に有用です。本記事で紹介する方法は、インプレース アップグレードではなく、新しいクラスタを用意して移行を行うアプローチに基づいています。今回は実際に実行するまでには至りませんが、今後のための備忘録として記載していきます。

### 今回使う材料

- AlloyDB for PostgreSQL
- Google Cloud Database Migration Service
- Terraform

## Database Migration Serviceの特徴と役割

> [データベース移行サービス](https://cloud.google.com/database-migration/docs/postgres-to-alloydb/overview)を使用して、PostgreSQLデータベースからAlloyDBクラスターにデータを移動できます。データベース移行サービスは、AlloyDBデータソース専用の構成を提供しませんが、AlloyDBはPostgreSQL互換であるため、一般的なPostgreSQLソース向けの構成を使用できます。

AlloyDBであってもDatabase Migration Serviceを利用することが可能です。このサービスは、AlloyDBがPostgreSQL互換である点を活用して、スムーズなデータ移行を実現します。特に、ダウンタイムを最小限に抑えたい場合や、複雑な手動操作を避けたい場合に非常に便利です。

## 移行元AlloyDBの設定手順

ソースクラスター内のすべてのインスタンスで[データベースフラグを構成](https://cloud.google.com/alloydb/docs/instance-configure-database-flags)します。次の値を使用します。

| フラグ                                                                                                                     | 設定値および説明                                                                                                          |
| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [`alloydb.logical_decoding`](https://cloud.google.com/alloydb/docs/reference/alloydb-flags#alloydb.logical_decoding)    | `on`に設定します。このフラグを有効にすることで、データベースの変更データキャプチャ（CDC）が可能になります。                                                         |
| [`alloydb.enable_pglogical`](https://cloud.google.com/alloydb/docs/reference/alloydb-flags#alloydb.enable_pglogical)    | `on`に設定します。PostgreSQLのロジカルレプリケーション拡張機能であるpglogicalを有効にするフラグです。                                                    |
| [`max_replication_slots`](https://www.postgresql.org/docs/15/runtime-config-replication.html#GUC-MAX-REPLICATION-SLOTS) | ソースインスタンスがサポートできるレプリケーションスロットの最大数を定義します。最低値は50です。計算式:`(データベースの数) * (同時に実行する移行ジョブ数) + (テーブル同期用のスロット数) + (現在のリードレプリカ用スロット数)` |
| [`max_wal_senders`](https://www.postgresql.org/docs/15/runtime-config-replication.html#GUC-MAX-WAL-SENDERS)             | `max_replication_slots`より少なくとも10大きい値に設定します。例: `max_replication_slots + 10 + (既存のWAL送信者数)`                         |
| [`max_worker_processes`](https://www.postgresql.org/docs/15/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES)      | `データベースの数 + 現在使用中のワーカープロセス数`で設定します。                                                                               |

現在の設定値を確認するには、次のクエリを実行します。このクエリを実行することで、`max_replication_slots`や`max_wal_senders`などの現在の設定値を把握できます。

```
SELECT name, setting
FROM pg_settings
WHERE name IN ('max_replication_slots', 'max_wal_senders', 'max_worker_processes');
```

出力例:

```
         name          | setting
-----------------------+---------
 max_replication_slots | 10
 max_wal_senders       | 10
 max_worker_processes  | 8
```

#### 設定値の計算方式

- **`max_replication_slots`**:
  計算式: `(データベースの数) * (同時に実行する移行ジョブ数) + (テーブル同期用のスロット数) + (現在のリードレプリカ用スロット数)`
  例: データベースが10個、移行ジョブが1つ、テーブル同期用スロットが5つ、リードレプリカが0の場合:

  ```sql
  max_replication_slots = 10 * 1 + 5 + 0 = 16
  ```

ただし、最小値が50である必要がるようなので計算結果が50未満であっても50にしておきます

- **`max_wal_senders`**:
  計算式: `max_replication_slots + 10 + (既存のWAL送信者数)`
  例: `max_replication_slots`が50、既存のWAL送信者が10の場合:

  ```sql
  max_wal_senders = 50 + 10 + 10 = 70
  ```

- **`max_worker_processes`**:
  計算式: `データベースの数 + 現在使用中のワーカープロセス数`
  例: データベースが10個で、現在のワーカープロセスが10の場合:

  ```sql
  max_worker_processes = 10 + 10 = 20
  ```

次に設定する適切な値を計算する基準として活用できます。


AlloyDBで設定を適用するためのTerraformコード例:

```hcl
resource "google_alloydb_instance" "default" {
  cluster       = "projects/${YOUR_PROJECT_NUMBER}/locations/asia-northeast1/clusters/${ALLOY_DB_CLUSTER_ID}"
  instance_id   = "INSTANCE_ID"
  instance_type = "PRIMARY"

  machine_config {
    cpu_count = 2
  }

  database_flags = {
    "alloydb.logical_decoding" = "on"
    "alloydb.enable_pglogical" = "on"
    "max_replication_slots"    = "50"
    "max_wal_senders"          = "60"
    "max_worker_processes"     = "20"
  }

  client_connection_config = {
    ssl_mode = "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
  }
}
```

### pglogicalの設定とユーザー権限構成

以下の手順で`pglogical`のインストールとセットアップを行います。

1. `psql`クライアントを使用してデフォルトの`postgres`データベースに接続します。

2. `pglogical`のインストール:

```sql
CREATE EXTENSION IF NOT EXISTS pglogical;
```

3. 移行データベースユーザーに必要な権限を付与します。

スキーマの確認:

```sql
SELECT nspname
FROM pg_namespace
WHERE nspname NOT IN ('information_schema')
  AND nspname NOT LIKE 'pg_%';
```

権限の付与:

```sql
GRANT USAGE ON ${SCHEMA_NAME} TO ${USER_NAME};
GRANT SELECT ON ALL TABLES IN ${SCHEMA_NAME} TO ${USER_NAME};
GRANT SELECT ON ALL SEQUENCES IN ${SCHEMA_NAME} TO ${USER_NAME};
```

4. `pglogical`を利用したロジカルレプリケーションの構成:

```sql
GRANT USAGE ON SCHEMA pglogical TO PUBLIC;
GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO ${USER_NAME};
ALTER USER ${USER_NAME} WITH REPLICATION;
```

インスタンス内に複数のデータベースがある場合は、同様の手順を各データベースで実行します。

### 移行先AlloyDBクラスターの準備

予め移行先のAlloyDBクラスターを用意します。

```hcl
resource "google_alloydb_cluster" "destination_alloydb" {
  cluster_id = "destination-alloydb"
  location   = "asia-northeast1"

  network_config {
    network = google_compute_network.default.id
  }

  database_version = "POSTGRES_16"

  initial_user {
    user     = "destination-alloydb"
    password = "destination-alloydb"
  }
}

resource "google_alloydb_instance" "destination_alloydb_primary" {
  cluster       = google_alloydb_cluster.destination_alloydb.name
  instance_id   = "destination-alloydb-primary"
  instance_type = "PRIMARY"
}
```
詳細は省きますが、ここで移行先のデータベースのバージョンを指定しておきます。
`database_version = "POSTGRES_16"` 今回はAlloyDBで指定できる最新の16にしておきます。

### 移行プロファイルの設定方法
プロファイルは、Database Migration Serviceがデータベースと通信するための設定です。プロファイルでは、接続するAlloyDBクラスターの詳細と認証情報を指定します。ただし、`username`や`password`などの機密情報はTerraformのtfstateファイルで管理するのは推奨されません。代わりに、環境変数やGoogle Secret Managerを利用して安全に管理することをお勧めします。
この設定により、データベース間の正確な接続が保証されます。
以下に設定例を示します。
#### 移行元の接続プロファイル作成
```hcl
resource "google_database_migration_service_connection_profile" "source_profile" {
  name     = "source-connection-profile"
  project  = var.project_id
  region   = var.region

  alloydb {
    cluster_id = var.source_alloydb_cluster_id
    username   = var.source_username
    password   = var.source_password
  }
}
```

#### 移行先の接続プロファイル作成
```hcl
resource "google_database_migration_service_connection_profile" "target_profile" {
  name     = "target-connection-profile"
  project  = var.project_id
  region   = var.region

  alloydb {
    cluster_id = google_alloydb_cluster.destination_alloydb.cluster_id
    username   = var.target_username
    password   = var.target_password
  }
}
```

### 移行ジョブの作成手順

移行ジョブは、Database Migration Serviceがデータを移行元から移行先にコピーし、継続的なデータ同期を行うためのプロセスを管理します。このジョブにより、データ移行の進行状況を監視したり、エラーのトラブルシューティングを行うことができます。ジョブの種類には以下のようなものがあります:

- **一度きりの移行ジョブ**: 最初にデータを完全にコピーし、変更データキャプチャ（CDC）は行いません
- **継続的な移行ジョブ**: データの初期コピーに加え、移行元データの変更をリアルタイムで移行先に反映します

以下は、継続的な移行ジョブの作成手順の例です。

```hcl
resource "google_database_migration_service_migration_job" "psqltoalloydb" {
  location         = "asia-northeast1"
  migration_job_id = "migration-id"
  display_name     = "alloydb-to-alloydb-migration-job"

  static_ip_connectivity {}

  source      = google_database_migration_service_connection_profile.source_profile.name
  destination = google_database_migration_service_connection_profile.target_profile.name
  type        = "CONTINUOUS"
}
```

`CONTINUOUS`にすることで継続的なレプリケーションを実行し、ダウンタイムを短縮できます。

### 移行ジョブの実行と注意点

ジョブの実行はコンソールで開始します。具体的には、Google Cloud ConsoleのDatabase Migration Serviceセクションで該当するジョブを選択し、[開始] ボタンをクリックします。ジョブ実行中には、進行状況をモニタリングできるダッシュボードが提供されます。なお、データベースへの負荷を避けるため、利用率が低い時間帯に実行することを推奨します。また、事前にバックアップを取得しておくと、万が一のトラブルに備えられます。

プライマリキーが存在しないテーブルでは、継続的なレプリケーションが実行できないため、対応が必要です。例えば、プライマリキーを追加するか、該当テーブルのデータを一時的に移行対象から外すといった対策が考えられます。詳細な手順や具体的な方法については[参考リンク](https://cloud.google.com/database-migration/docs/postgresql-to-alloydb/debugging-tools#find_tables_without_primary_keys_pks)をご参照ください。

### まとめ

以上で、Database Migration Serviceを使用したAlloyDB for PostgreSQLのバージョンアップデート設定が完了しました。
確実にこのままでは動かなそうな雰囲気はありそうですが大まかな設定はこのような形になるのではないかなと思います。
今回はマイグレーションを実際に実行するところまでは進めませんでしたが、次回は移行の実施や必要なダウンタイムの見積もりまで進めたいと考えています。

### 参考文献

- [https://cloud.google.com/alloydb/docs/cluster-upgrade](https://cloud.google.com/alloydb/docs/cluster-upgrade)
- [https://cloud.google.com/database-migration/docs/postgresql-to-alloydb/quickstart](https://cloud.google.com/database-migration/docs/postgresql-to-alloydb/quickstart)
- [https://cloud.google.com/alloydb/docs/cluster-upgrade#use-database-migration-service](https://cloud.google.com/alloydb/docs/cluster-upgrade#use-database-migration-service)
- [https://engineering.dena.com/blog/2024/06/pca\_portability\_gcp\_dms/](https://engineering.dena.com/blog/2024/06/pca_portability_gcp_dms/)