Amazon RDS for PostgreSQLバージョンを 10 から 14 へ上げた話

エムスリーキャリアでエンジニアをしている山本です。最近筋トレでフロントレバーをしていて首を痛めました。

この記事では「RDS for PostgreSQLのバージョンを10系から14系に上げた話」をしたいと思います。
主に「対応の進め方」を振り返り、反省して気づいたことを記載していきます。

## サマリー

- 対応内容の振り返り
- よかった点、改善点

## 前提知識

- Ruby on Rails と PostgreSQLが稼働しており、それぞれ Fargate, RDS で動作しています

## 対応内容の振り返り

自身が行った対応内容を実際に行った順番に書き連ねていきたいと思います。

サマリーとしては下記となります。

1. ローカル環境での検証
2. Gitlab CI上で動作するPostgreSQLコンテナイメージのバージョンアップ
3. QA環境での非SSL接続検証
4. 本番環境でのバージョンアップ検証
5. 非互換性の変更調査
6. セキュリティパッチの調査
7. AWSが推奨する確認の実施
8. 実施手順の詳細化
9. ビジネス側とのメンテナンス日程調整
10. QA環境へのメンテナンス実施
11. リグレッションテストの実施
12. 本番へのメンテナンス実施

### ローカル環境での検証

ローカル環境にてPostgreSQL14を立ち上げ、そのDBに接続した上で rspec に成功することを確認しました。

### Gitlab CI上で動作するPostgreSQLコンテナイメージのバージョンアップ

弊社ではGitlabを利用しています。
作成したマージリクエストに対してrspecを実行し、問題なければQA環境へ反映が行えるようなCIの仕組みを構築しています。

CIの中で起動している PostgreSQL コンテナのバージョンも14に上げるため対応を行いました。
Gitlab CI はECRにプッシュされたイメージを利用してコンテナを立ち上げています。
具体的にはローカル上で build したイメージをECRへプッシュするような対応を行いました。

ローカルのPCがM2チップを搭載したMacBook Proだったということを意識できておらず、Gitlab Runner側のCPUアーキテクチャとの違いから中々 rspec の実行がうまくいきませんでした。

### QA環境でのバージョンアップ検証

QA環境（動作確認を目的とした環境）でバージョンアップの検証を試みました。
スナップショットから復元して新たに構築したDBのバージョンアップを試みたのでQA側の作業に影響を与えることなく検証を行っています。

結果としては「パラメータグループの設定値 ssl を 0 に設定できなくなった」という事実が発覚しました。
[パラメータグループの設定値 ssl](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.Parameters.html) は「SSL 接続を有効にするかどうか」に関わる設定値です。
今までは 0 と設定値 SSL の接続を無効にしてたのですが、デフォルト値が1でその値が更新できなくなっていました。

以上より、バージョンアップ以降から証明書更新の運用を検討する必要が出てきました。

この事実を受けて、
- 既存通りにクライアントは非SSLでDBに接続したい
  - 暗号化通信を新しく実施する分のコンピューティングリソースの圧迫なども懸念
  - プライベートなネットワーク内で通信しており、インターネット経由での通信は無いため必須ではないと判断
- サービスを極力停止したくなく、証明書更新のためのダウンタイムも可能であれば回避したい

の理由から

- クライアントが非SSLでDBに接続する方法の模索
- 証明書の更新を行わない手段はないかサポートに相談

という次のアクションが決まりました

#### analyze verbose で WARNING

メンテナンス実施手順の中に「analyze verbose」の実施があります。
QA環境にて実施したところ下記の大量な警告が表示されました


```
WARNING:  skipping "pg_toast_1262_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_2964_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_1213_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_1260_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_2396_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_6000_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_3592_index" --- only superuser can analyze it
WARNING:  skipping "pg_toast_6100_index" --- only superuser can analyze it
WARNING:  skipping "pg_database_datname_index" --- only superuser can analyze it
WARNING:  skipping "pg_database_oid_index" --- only superuser can analyze it
WARNING:  skipping "pg_db_role_setting_databaseid_rol_index" --- only superuser can analyze it
WARNING:  skipping "pg_tablespace_oid_index" --- only superuser can analyze it
WARNING:  skipping "pg_tablespace_spcname_index" --- only superuser can analyze it
WARNING:  skipping "pg_authid_rolname_index" --- only superuser can analyze it
WARNING:  skipping "pg_authid_oid_index" --- only superuser can analyze it
WARNING:  skipping "pg_auth_members_role_member_index" --- only superuser can analyze it
```

記載内容の通り、analyze verbose 実行時のユーザに適切な権限が付与されていないことが原因でした。
これを受けて、「RDSマスターユーザで analyze を実施」するように手順を修正しています

### QA環境での非SSL接続検証

1の検証結果を受けて非SSLでの接続方法を模索したところ[PGSSLMODE環境変数の設定](https://www.postgresql.jp/document/14/html/libpq-envars.html)が見つかりました。

クライアントであるrailsアプリケーションはfargateで稼働しているため、タスク定義に環境変数PGSSLMODE: disable を設定することで非SSLでの接続が行われることを確認しました。

```
SELECT
	datname AS "Database name",
	usename AS "User name",
	ssl,
	client_addr,
	application_name,
	backend_type
FROM
	pg_stat_ssl
JOIN pg_stat_activity
   ON
	pg_stat_ssl.pid = pg_stat_activity.pid
ORDER BY
	ssl;
   Database name   |       User name       | ssl | client_addr  |                  application_name                  |  backend_type
-------------------+-----------------------+-----+--------------+----------------------------------------------------+----------------
 database | user_name | f   | 10.x.x.x | client_application | client backend
 database | user_name | t   | 10.x.x.x | psql                                               | client backend
```

上記クエリの実行結果から ssl: false のためrailsアプリケーションからは非SSLで接続されていると判断できます。

### 本番環境でのバージョンアップ検証

QA環境での検証結果を教訓に、本番で主に実施したい作業を検証しました。
本番DBのスナップショットから復元したDBに対して検証を実施しています。

この作業の目的としては「本番と同等の条件で実施した場合の所要時間計測」です。
この計測結果をもとに「メンテナンスに掛かる所要時間を踏まえてビジネス側とメンテナンス日程と時間帯を調整」することが目的でした。

検証では下記を計測しました
- スナップショット作成にかかる時間
- スナップショットからの復元にかかる時間（問題発生からの切り戻し時を想定）
- バージョンアップにかかる時間
- 全テーブル Analyze にかかる時間

結果として総じて大体1時間があれば行えるという結果となりました。
そこから切り戻し判断後の切り戻し作業も考慮に含めて2時間程度メンテナンスを目的にサービスを停止させてほしい旨ビジネス側と調整を行うことにしました。

### 非互換性の変更調査

今回はpostgres10 -> 14 へのアップデートを実施します。
それまでのメジャーバージョンアップの中で非互換性のある変更を調査しました。

例えば[14のメジャーバージョンアップデート情報はこちらです。](https://www.postgresql.org/docs/release/14.0/)

各バージョンを翻訳の上、メンテ対象となるアプリケーションで非互換性のある変更と関連のある箇所を利用していないかを確認しました。

具体的には変更内容から思いついた事柄を確認し、確認結果から問題ないと判断した理由まで書き添えてまとめています。

### セキュリティパッチの調査

[公式の14系における既知のセキュリティ脆弱性情報](https://www.postgresql.org/support/security/14/)に照らし合わせて、「今回対象となるバージョンで対応されていない脆弱性はないか」と言う確認で確認を行いました。

「その攻撃は今回のサービスでは可能か」「可能とした場合のビジネス的な影響」という観点でメンバーと相談しながら対応方針を確定させていきました。


### AWSが推奨する確認の実施

AWSが [RDS PostgreSQLのアップグレードの際に推奨される手順](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.PostgreSQL.html#USER_UpgradeDBInstance.PostgreSQL.MajorVersion.Process)を公開しています。

これを元に必要な対応が無いかを調査しました。

Reg*データ型、エクステンション、リードレプリカへの考慮など自力では思いつけなかった観点がいくつかあり参考になりました。

例えばインストール済みのエクステンションは下記で確認しています。

```sql
SELECT * FROM pg_available_extensions WHERE installed_version IS NOT NULL;
  name   | default_version | installed_version |           comment
---------+-----------------+-------------------+------------------------------
 plpgsql | 1.0             | 1.0               | PL/pgSQL procedural language
(1 行)
```

以上より対応が必要なエクステンションは利用されてないと判断し、対応不要と結論づけました。

### 実施手順の詳細化

- 基本的にコマンドをコピペするだけで良い
- 実行後の結果から何を見て問題ないと判断できるか明確にする
- 何が目的でこの手順を実施しているのかがわかる
- どのタイミングまでには切り戻し判断をするかが明確になっている
- 切り戻しの手順まで明記されている

上記を意識しながら実施手順の詳細化を図りました。
自分なりに書いた後にエンジニア・QAメンバーでレビュー会を実施しました。

- 実施した手順を後始末するための手順が必要では
  - テスト目的でアップロードしたファイルの削除など
- この手順はそのタイミングだと無理ではないか？
  - バッチの動作確認の手順があるが、この時点ではまだバッチが起動していない
- 動作確認目的のバッチ実行時ユーザは適切か？
- 別のシステムが依存しているが、サービス停止時のシステム影響にどう対応するか
- この機能も動作確認したほうが良いのでは
- メンテナンス中のビジネス側への連絡はこのタイミングで一報入れたほうが

など、様々な観点からの指摘をいただくことができ未然に本番メンテナンス失敗のリスクを低減させることができたと思います。
仕上がった手順はレビュー頂いたメンバーの協力あっての賜物だと思っています。ありがとうございました。

### ビジネス側とのメンテナンス日程調整

ビジネス側と調整を行う際には検討材料として下記を用意しました

- アクセス頻度を表すグラフ
  - 最もアクセスが来ている時間帯でのメンテナンスを避ける目的
- メンテナンスに必要な時間
  - 実施手順・「本番環境でのバージョンアップ検証」で明らかになった所要時間を元に算出
- バッチのスケジュール情報
  - ビジネス的に止めたくないバッチ処理を実行する時間帯は避ける目的
- 依存しているシステムの影響度合い
  - メンテナンス実施時のページを見ていただき、許容できるか判断いただく目的

上記の情報をもとにビジネス側の方と話し合い、メンテナンス実施日時を確定させました。
しかし、まだQA環境でメンテナンス実施の上リグレッションテストを行っていません。そのため「テスト失敗して、本番への実施は延期になるリスク」はありました。

このタイミングで日程調整を行った理由は下記からでした

- 全てを終えたタイミングで日程を調整すると、確定したメンテナンス日時までの間時間が空く
  - 時間が空くと、このことを徐々に忘れて当日の動きが鈍くなることを懸念した
  - 細部まで手順化しているつもりだが、脳内の記憶を呼び起こすための時間がかかり作業がスムーズにいかないと想像した
- 「非SSL接続の検証」、「メジャーバージョンのリリースノートから非互換性のある変更チェック」、「セキュリティパッチのチェック」、「AWS公式の推奨確認事項の確認」から、およそ大きな問題は残っていないと判断した

### QA環境へのメンテナンス実施

設定した手順に基づいてQA環境へメンテナンス作業を行いました

本番環境で実施する予定の手順をなぞったことで、「ちょっとした本番環境へのメンテナンスリハーサル」が行えた点でより良かったと考えています。

この対応で「手順を実施しながらその証跡を残す作業を並行して行う」ことに気づき、手順の一つ一つが記録の分だけ時間がかかるという気づきがありました。

### リグレッションテストの実施

バージョンアップが完了したQA環境でリグレッションテストを行いました。

このテストの完了を持って、本番でのメンテナンス実施可否が決まります。

前述した幾重もの確認（リリースノートからの非互換性のある変更チェックなど）が功を奏したのか、問題が見当たらず本番環境でメンテナンス実施を進めるという判断となりました。

### 本番へのメンテナンス実施

実はまだ本番へのメンテナンスは執筆時点（2022 12/22）では実施していません。
メンテナンス対象となるサービスがちょうどキャンペーン期間中のため、それが終了してからメンテナンスを実施するという判断になっています。

リグレッションテストをはじめリリースノートのチェックなど、これでもかというほど検証を重ねたので割と不安な気持ちは小さいです。

これほどの確認をしたにも関わらず本番メンテナンスで何か予想外のことが発生した際は改めてそのナレッジを共有できたらと思います。

## 良かった点、改善点

### 【良かった】リリースノート・セキュリティパッチなどの調査を行ったこと

10 -> 14系へのアップデートという思い切った判断のため、当初他のエンジニアに相談した際はリスクが大きいのではという声を頂いていました。

その際に安心材料として調査結果を共有できたことが、調査を実施して良かった点だと考えています。

自分自身も大きかった不安が、調査を完了させる頃には多少小さくなったと思っています。

### 【良かった】周りのサービスで前例のない14系のバージョンアップを実施できた

自身が関わるチームの周りでPostgreSQLを14系に上げているサービスはまだありませんでした。

- メンテナンス対象のサービスはビジネス的なインパクトがそれほど高くない

という事実から、「低リスクで貴重なナレッジが貯められた」ことが良かったと思っています。

今回の対応で分かった

- SSLパラメータが１にならざるおえない
- 証明書更新の運用を新たに検討が必要

などを横展開し、他サービスのメンテナンスがスムーズにいくと良いなと思っています。

### 【良かった】エンジニア・QAメンバーを巻き込んで手順のレビュー会が実施できた

「実施手順の詳細化」で述べたようなレビュー会を行うことで下記が良かったと考えています。

- 筆者が参画して１ヶ月と少しなので、レビュー会を通してシステムの全体像が把握できるようになった
- 細かく１つ１つの手順に集中するあまり、全体を見渡した時のおかしな点について指摘が頂けた
- レビュー会を通して対応の中で得られた知見を共有することができた

### 【良かった】課題管理・工程管理表を作ったこと

RDSメンテナンスの目処が建つまでにいくつもの課題が生じ、クリアした後に課題が生まれの繰り返しでした。
17程度の課題を挙げ、それぞれに対し調査を行い分かった事実から結論を出していきました。

その際、1つも忘れることなく課題を解決していくために課題管理シートを作成していたのは良かったと考えています。

1つのスプレッドシートに挙げた課題とそれぞれの調査結果をシート毎でまとめたりしたのですが、シート数が22程度になりました。
目的のシートにたどり着くのが難しくなり、そこは改善の余地があったかなと思っています。

課題管理表では主に下記の情報を記載しながら管理していました

- 起票日
- 起票者名
- ステータス（未完 or 完了）
- 課題
- 課題の詳細
- 解決条件
- 結論
- 記録（調査メモ的位置付け）

中でも「解決条件」を設けたことで、この課題はどうなったら解決なのだっけといちいち思い出さずに済んだことが良かったと考えています。

工程管理表も「ビジネス側との調整」をはじめ「QAチームとのリグレッションテスト日程調整」など様々な達成すべき目標をサマることができ、自分自身が今どの辺にいてどこに向かえば良いかが明るくなった点で良かったと思っています。

### 【反省】 Gitlab CI上で動作するPostgreSQLコンテナイメージのバージョンアップは後回しでよかった

ローカル検証の後にCI上もPostgreSQL14で動作する対応を行ったのですが、それよりも「検証・調査を優先してなるはやでビジネスとの調整を行えばよかった」と思っています。

### 【反省】 ビジネス側へは頭出しだけでもしておけばよかった

「不確定要素を多く抱えたままビジネス側と調整を図っても何も決まらないだろう」という思いから調査・検証を完了させたタイミングで調整を行いました。

一方で「本番メンテナンスを予定してます」程度の共有をすることで
- ビジネス側も心の準備ができるし、調査・検証後の調整がスムーズに進みそう
- ざっくりとビジネス側の状況を把握することで筆者側の調査・検証をいつまでに行えば良いかの目算が立てられそう

なメリットがあったろうと今は感じています。