IBM Cloud Databases for PostgreSQL のバージョンアップ検証

# はじめに
**IBM Cloud Databases for PostgreSQL v12が2025/1/22にEOSになると発表がありました。**
[参考：IBM Cloud® Databases for PostgreSQL のリリース・ノート](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-postgresql-relnotes)

**PostgreSQLの最新バージョンであるv15へのアップデートが推奨されています。2025/1/22以降(有効期終了後)には、バックアップが作成されるものの、DBインスタンスへのアクセス権限が削除され、アクセスが出来なくなりますので早めにバージョンアップをする必要があります。**


[参考：バージョン管理ポリシー](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-versioning-policy)

 
# PostgreSQLのバージョンアップ方法
**この記事では、IBM Cloud Docsに記載されている以下２つのバージョンアップ方法について検証した手順結果を記載いたします。**
①読み取り専用レプリカをプロモートしてバージョンアップする方法　
②バックアップを新規デプロイメントにリストアする方法

[参考：新しいメジャーバージョンへのアップグレード](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-upgrading&interface=ui)


また他にも、停止時間を限りなく短くする目的で、Logical Replication(テーブルやデータベースごとに複製するレプリケーション)を利用したバージョンアップ方法もあります。こちらの方法の検証は、この記事では扱いません。こちらの方法については、以下のリンクをご参考ください。
[参考：Upgrading IBM Cloud Databases for PostgreSQL with Minimal Downtime](https://www.ibm.com/blog/upgrading-ibm-cloud-databases-for-postgresql-with-minimal-downtime/)

# それぞれのメリット・デメリットについて
## 方法①読み取り専用レプリカをプロモートしてバージョンアップする方法
**メリット**
* 読み取り専用レプリカのプロモート直前にアプリケーションからの接続を停止すれば良いため、バックアップ取得時点からアプリケーションからの接続を停止する必要がある方法②より、アプリケーション接続の停止時間が短く済む。
* バージョンアップ用に作成した読み取り専用レプリカを用いて、プロモート前にアプリの接続検証（更新は不可）ができる。

**デメリット**
* IBM Cloudポータル画面ではバージョンアップが出来ない。
* 読み取り専用レプリカを稼働している間は、リーダー・デプロイメント(「レプリカ」に対する元DBデプロイメントの呼称)と同様に課金が発生する。
* プロモート完了まで高可用性を担保するメンバーが存在しない。

## 方法②バックアップを新規デプロイメントにリストアする方法
**メリット**
* IBM Cloudポータル画面で操作することが出来るので、少ない手順で完了出来る。
* 必要に応じて、バックアップを過去7日間のものから選択することができる。
* バージョンアップ開始のタイミングで明示的にバックアップをとってからリストアするので、アプリ・データ更新の静止点をデータベース側で確保でき、データの不整合を防ぐことが出来る。

**デメリット**
* 明示的にバックアップを取らなければ、自動バックアップ以降に追加したデータは同期されないままリストアされてしまう。
* ①と比較して、保守開始のタイミングからバックアップとリストアを実施するため、①のプロモートよりはアプリケーションの停止時間が長くなる。

***
上記のメリット・デメリットにおいて、保守時間の観点を考慮すると方法①がおすすめです。プロジェクト要件に応じて選択ください。

また、どちらの方法もアプリケーションからのアクセス先情報が変わりますので、アプリケーション側で接続し直しが必要となります。データの整合性を担保するために、ある程度保守の時間をとって作業を行うと良いと思います。詳細は本記事の最後の [アプリケーションの接続先変更について](https://qiita.com/egawaaa/private/0204c5fb8e65e1b99f2e#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%8E%A5%E7%B6%9A%E5%85%88%E5%A4%89%E6%9B%B4%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)をご参照ください。

# 方法①読み取り専用レプリカをプロモートしてバージョンアップする方法
## 検証環境
IBM Cloud Database PostgreSQL
デプロイメント名：qiitatest-Databases for PostgreSQL-sr
バージョン：v11
ロケーション：東京
Disk Encryption Key: Automatic Key
Backups Encryption Key: Automatic Key

リソース
* RAM: 1G
* Disk: 5G
* Cores: 0
* Member: 2
![Box_Notes画像_2024-02-07_15_55_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/bdcd7292-b903-7949-6ae7-64e4dd4068c5.png)
## 1. リーダー・デプロイメントの読み取り専用レプリカを作成
読み取り専用レプリカは、非同期レプリケーションを介して、リーダー・デプロイメントからレプリカ・デプロイメントにすべてのデータをレプリケーションするようにセットアップされます。
[参考：読み取り専用レプリカに関する考慮事項](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-read-only-replicas&locale=ja#read-only-replicas-consider)
また、読み取り専用レプリカの作成中もリーダー・デプロイメントは通常通り稼働します。

IBM Cloudポータルで、リーダー・デプロイメントの詳細画面左側にあるRead-only replicasという欄を開きます。

Createをクリックし、読み取り専用レプリカを作成。
![スクリーンショット_2024-02-24_12_19_50-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/a718d48b-4f1a-1634-f656-36fa508add3e.png)

![スクリーンショット_2024-02-24_12_28_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/5581a5c4-f499-fcff-8fcb-8c861f0b9f4e.png)
**読み取り専用レプリカのプロビジョニングに際して選択する項目**
* DBを動かすプラットフォーム
* 読み取り専用レプリカのサービス名、リソースグループ、ロケーション¥
* リソースアロケーション
* サービスコンフィグレーション(この項目はリーダー・デプロイメントから変更できません)


新規にリソースを作成することになる為、課金が発生する点にご留意ください。
今回は以下のように選択しました。
* IBM Cloud Platform
* Databases for PostgreSQL-pq, Common, jp-tok
* RAM 1GB, Disk 5GB, IOPS 50, Core 0
* version 15, Automatic Disk Encryption, Public&Private Endpoints

Createを押してプロビジョニングを開始します。


IBM Cloudポータル画面から、読み取り専用レプリカが作成されたことが確認できます。
![スクリーンショット_2024-02-26_11_36_26-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/c1f9cea1-dc73-67d3-e589-5aada4c7ddea.png)

## 2. リーダー・デプロイメントと、読み取り専用レプリカの同期状況を確認
リーダー・デプロイメントにて、レプリケーションの状態を確認するためのコマンドを打ってみます。
[参考：レプリケーション状況の確認](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-read-only-replicas&locale=ja#checking-replication-status)
```ruby: PostgreSQL バージョン 10 以上の場合
SELECT * FROM pg_stat_replication;
```
以下の出力が返ってきました。
```
  pid   | usesysid |     usename     |              application_name              |  client_addr   | client_hostname | client_port |         backend_start         | backend_xmin |   state   |  sent_lsn  | write_lsn  | flush_lsn  | replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state
--------+----------+-----------------+--------------------------------------------+----------------+-----------------+-------------+-------------------------------+--------------+-----------+------------+------------+------------+------------+-----------+-----------+------------+---------------+------------
 106673 |    16479 | ibm-replication | c-42a09b48-a8cd-42ce-abd7-74717b31f87a-m-0 | 172.30.xxx.x   |                 |       40468 | 2024-02-26 02:30:27.787576+00 |              | streaming | 3/A20007E0 | 3/A20007E0 | 3/A20007E0 | 3/A20007E0 |           |           |            |             0 | async
    283 |    16479 | ibm-replication | c-798e01c5-5500-4ad9-ab81-02f03911c682-m-1 | 172.30.xxx.xxx |                 |       59426 | 2024-02-22 19:11:33.280839+00 |              | streaming | 3/A20007E0 | 3/A20007E0 | 3/A20007E0 | 3/A20007E0 |           |           |            |             2 | sync
(2 rows)

```
1行目が読み取り専用レプリカの情報です。確認すべき項目は、以下の通りです
|項目|出力|
|:---:|:---:|
|state|streaming|
|sync_state|async|

読み取り専用レプリカは非同期レプリケーションを行なっているので、ストリーミング状態に入り最新の変更を適用している上記の状態で、正常にレプリケーションが進行していると言えます。

また、2行目の”sync”となっているものは、プライマリサーバに障害があった時の切り替え先のノードです。こちらは常に同期されています。
[参考：pg_stat_replicationの詳細](https://www.postgresql.jp/document/15/html/monitoring-stats.html#MONITORING-PG-STAT-REPLICATION-VIEW)

## 3. 読み取り専用レプリカを、バージョン指定してプロモート
読み取り専用レプリカにデータがレプリケーションされていることが確認できたので、ここで読み取り専用レプリカからプロモートします。同時にアップグレードするバージョンも指定します。

:::note info 
プロモートとは 
読み取り専用レプリカを、読み取り操作だけでなく書き込み操作も受け入れる独立したデプロイメントにすること。 
リーダー・デプロイメントに問題が発生した場合に、読み取り専用レプリカをスタンドアロンインスタンスにプロモートすることで、アプリケーションからの書き込みの受け入れを開始することができます。
:::

:::note warn
プロモートしたスタンドアロンインスタンス(元読み取り専用レプリカ)は、読み取り専用レプリカに戻すことも、リーダー・デプロイメントに再参加させることもできなくなります。
:::

:::note warn
バージョンアップを行う前に、`pg_repack`がインストールされている場合は削除する必要があります。次のようなコマンドを使用して実行してください
```
DROP EXTENSION pg_repack;
```
バージョンアップが完了した後に、再度インストールを行なってください。
:::

IBM Cloudポータル画面からプロモートすることも出来ますが、その際はバージョンを指定することが出来ません。今回は読み取り専用レプリカを利用してバージョンアップをすることが目的のため、UIでプロモートは行いません。

以下のAPIでプロモートとバージョンアップをドライラン/実行します。
[参考：読み取り専用レプリカからのアップグレード](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-upgrading&interface=ui#upgrading-replica)

```ruby:ドライラン
curl -X POST   https://api.<region>.databases.cloud.ibm.com/v5/ibm/deployments/<id>/remotes/promotion
-H 'Authorization: Bearer <xxxxxxxx>'
-H 'Content-Type: application/json'
-d '{
    "promotion": {
        "version": "15",
        "skip_initial_backup": false
        "dry_run": true
    }
}'
```


```ruby:プロモート
curl -X POST   https://api.<region>.databases.cloud.ibm.com/v5/ibm/deployments/<id>/remotes/promotion
-H 'Authorization: Bearer <xxxxxxxx>'
-H 'Content-Type: application/json'
-d '{
    "promotion": {
        "version": "15",
        "skip_initial_backup": false
    }
}'
```
**デプロイメント情報を参照し、上記API内の以下の項目を入力してください。**
* ```<region>```：DBリソースのあるリージョン
* ```<id>```：デプロイメントID(CRN値に含まれるスラッシュ```/```を```%2F```に変更します[参考：Deployment IDs and CRNs](https://cloud.ibm.com/apidocs/cloud-databases-api/cloud-databases-api-v5#deployment-ids-and-crns)
* ```Authorization Bearer <xxxxxxxx>```：アクセストークン
[参考：アクセストークンの取得](https://cloud.ibm.com/docs/key-protect?topic=key-protect-retrieve-access-token&locale=ja)
*  ```version```：適用したいバージョン
* ```skip_initial_backup```：オプション
スキップすると、プロモーションの完了時に初期バックアップを実行しません。 スタンドアロンインスタンスは、次回の自動バックアップが実行されるか、オンデマンド・バックアップを実行するまでバックアップされないというリスクはありますが、短時間で使用可能になります。
* ```dry_run```：オプション
実行前にドライランで確認することを推奨します。

```ruby:出力例
{"task":{"id":"crn:v1:bluemix:public:databases-for-postgresql:jp-tokxxxxxxxxxxx","description":"Promoting read-only replica to standalone instance and upgrading to version 15","status":"running","deployment_id":"crn:v1:bluemix:public:databases-for-postgresql:jp-tok:yyyyyyyyyyyyyyyy::","progress_percent":5,"created_at":"2024-03-01T08:38:07.000Z"}}%
```
こちらの出力が確認できればバージョンアップが進行していることがわかります。
```"Promoting read-only replica to standalone instance and upgrading to version 15"``` , ```"status":"running"```


## 4. バージョンアップ完了を確認
IBM Cloudポータル画面から確認すると、v15にバージョンアップされていることがわかります。
![スクリーンショット_2024-03-01_18_31_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/9ff808de-20e7-faed-c236-a7df92c273ec.png)


# 方法②バックアップを新規デプロイメントにリストアする方法
## 検証環境
方法①と同じく```qiitatest-Databases for PostgreSQL-sr```デプロイメントを使用します。
## 1. バックアップを指定
デプロイメントの詳細画面左側、backups and restoreの欄を表示し
ます。リストアしたいバックアップを、最新、又は過去7日間のうちの特定の時点のバックアップから選択することが出来ます。日時で自動バックアップが取られていますが、最新のデータを反映するにはリストア前に、バックアップをとることを推奨します。
![スクリーンショット_2024-03-04_14_59_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/713179f1-978b-4bd9-eb31-bc4b8b441902.png)
![スクリーンショット_2024-03-01_0_29_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/786ddeec-f308-647c-b6a2-1d90ef90e55e.png)

## 2.バックアップをリストアする新規デプロイメントをプロビジョニング
Restore backupをクリックすると、リストア先の新規デプロイメントをプロビジョニングする画面に遷移します。
![スクリーンショット_2024-03-01_19_57_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/3c125401-457e-5c6e-32ca-03b1655e5366.png)

![スクリーンショット_2024-03-01_19_59_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/5261168d-a444-e06a-ba37-792008046e15.png)

**新規デプロイメントをプロビジョニングする際に選択する項目**
* DBを動かすプラットフォーム
* 読み取り専用レプリカのサービス名、リソースグループ、ロケーション
* リソースアロケーション
* サービスコンフィグレーション

新規にリソースを作成することになる為、課金が発生する点にご留意ください。

今回は以下のように選択しました。
* IBM Cloud Platform
* Databases for PostgreSQL-pq, Common, jp-tok
* RAM 1GB, Disk 5GB, IOPS 50, Core 0
* version 15, Automatic Disk Encryption, Public&Private Endpoints
:::note warn
デフォルトでは、**新規デプロイメントは、リストア元のバックアップ時のソース・デプロイメントと同じディスクおよびメモリー割り振りに自動的にサイズ変更**されます。 特に、デプロイメントの現在のサイズではない可能性がある PITR の場合には注意してください。 新規デプロイメントに割り振るリソース量を調整する必要がある場合は、新しいデプロイメントのサイズを変更します。 データとワークロードに十分な量を割り振ってください。**デプロイメントに十分なリソースが指定されていない場合、リストアは失敗します。** 
:::


最後に、右下のPoint in time recoveryをクリックして、リカバリーを開始します。

:::note warn
**バックアップのリストア中は、ソース・デプロイメントを削除しないでください。** 古いデプロイメントを削除したい場合は、新しいデプロイメントがプロビジョンされてバックアップがリストアされるまで待機してください。デプロイメントを削除すると、そのバックアップも削除されるため、リストアが失敗するだけでなく、バックアップもリカバリーできない可能性があります。
:::
プロビジョニング完了まで、少し時間がかかります。
![スクリーンショット_2024-03-01_20_05_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/8d6c039f-defb-e8f7-9c33-a6d4f7ee145a.png)

## 3. バージョンアップ完了を確認
新しいデプロイメントのプロビジョニングが完了しました。v15にバージョンアップできていることが確認できます。
![スクリーンショット_2024-03-04_9_03_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/bdce436d-38a1-07ff-663a-3a0c65db5c2d.png)


# アプリケーションの接続先変更について
**①②どちらの方法でバージョンアップしても、アプリケーションからの参照先DBを変更するために、プログラムの中の接続先情報を変更する必要があります。** 以下、接続先変更作業の検証をしました。

## 検証環境
個人用PCから、IBM CloudのVitual Server Instance上にPHPで作成したwebサイトにFloatingIPでアクセスします。PHPで作成したサイトは、あらかじめPostgreSQL内のテーブルをPrivate Endpointを経由して呼び出すようコードを書きました。
![スクリーンショット 2024-03-06 21.11.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/4b2ef8bc-c23c-4f7e-1129-e1ccb7d82112.png)
### PostgreSQL
バージョンアップ検証で使ったものと同じデプロイメント```qiitatest-Databases for PostgreSQL-sr```です。


![Box_Notes画像_2024-02-07_15_55_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/bdcd7292-b903-7949-6ae7-64e4dd4068c5.png)

### Vitual Server Instance
![スクリーンショット_2024-03-06_21_45_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/7e656d36-cb4e-efec-0705-f95a471d1daf.png)

## 切り替え例：読み取り専用レプリカをプロモートしてバージョンアップする場合
![スクリーンショット 2024-03-08 11.25.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/457b06db-b39d-1473-99f1-9679233eca9a.png)

PHPでの接続を例とする場合は、プログラムの中の接続情報のhostとportを書き換えます。Service Credential内の情報を参照してPHPファイルに記載します。以下、スタンドアロンインスタンスに接続する場合のPHPプログラムの例です。

```ruby:replica.php
<?php
function convert_enc($str){
    $from_enc = 'EUC_JP';
    $to_enc = 'SJIS';

    return mb_convert_encoding($str, $to_enc, $from_enc);
}

$dsn = 'pgsql:dbname=ibmclouddb host=xxxxxxxxxxxxxxxxx.databases.appdomain.cloud port=yyyyy';
$user = 'admin';
$password = '2988.....';

try{
    $dbh = new PDO($dsn, $user, $password);

    print('スタンドアロンインスタンスへの接続に成功しました。<br>');

    $sql = 'select * from users';
     foreach ($dbh->query($sql) as $row) {
      print($row['id'].'<br>');
      print($row['firstname'].'<br>');
      print($row['lastname'].'<br>');
  }
}catch (PDOException $e){
    print('Error:'.$e->getMessage());
    die();
}


$dbh = null;
~
```
このうち、接続先として書き換えた部分は、以下の通りです。
```Diff_php
$dsn = 'pgsql:dbname=ibmclouddb
host=xxxxxxxxxxxxxxxxx.databases.appdomain.cloud 
port=yyyyy';
```


**書き換える必要があるもの**
```host```, ```port```

**引き続き使えるもの**
```user```,```password```, ```Service Credential```


## 接続確認
接続先を変更したPHPファイルにブラウザからアクセスしたところ、スタンドアロンインスタンス内のテーブルが表示されました。古いバージョンのデプロイメントから、スタンドアロンインスタンスへと接続先を変更出来たことがわかります。
![スクリーンショット 2024-03-01 19.06.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3523945/d2fde07f-5e9d-a55e-3920-869edd148e97.png)

## バージョンアップ作業に伴うサービス停止時間
バージョンアップ作業に際して、DBを利用しているサービスを停止する必要のある時間が発生します。タイミングは以下の通りです。
(テストデータ：562MB, RAM 1GB, Disk 5GB, IOPS 50, Dedicated Core 0)　

### 方法①読み取り専用レプリカをプロモートしてバージョンアップする場合
(①-1)読み取り専用レプリカのプロモートが完了するまでの時間：16分25秒
(①-2)アプリケーション側の接続先変更作業中
(①-3)テスト作業中

### 方法②バックアップを新規デプロイメントにリストアする場合
(②-1)バックアップの取得を開始してから完了するまでの時間：1分40秒
(②-2)新規デプロイメントにバックアップをリストアする時間：17分23秒
(②-3)アプリケーション側の接続先変更作業中
(②-4)テスト作業中

***
なお、上記はあくまで検証結果であり、全てのケースにこの検証結果が当てはまるわけではない点についてはご了承ください。

# まとめ
PostgreSQLのバージョンアップ方法２つを検証してみました。
どちらも停止時間を０で作業することは出来ないため、保守作業時間やデータベースの更新を止めるタイミングなどを考慮して、より良い方法で行っていただくといいと思います。