🔀

Blue/Green デプロイを使用した、RDS MySQL/PostgreSQLのアップグレード

2024/03/21に公開

TL;DR

  • RDS の メジャーバージョンアップグレード を行なった
    • PostgreSQL 11.6 -> 15.5
    • MySQL 5.7.44 -> 8.0.36
  • PostgreSQL は AWS CDK を利用した、自前での手動切り替えをベースにした Blue/Green デプロイによるアップグレードを行なった
  • MySQL は AWS コンソールから AWSが提供している機能である RDS Blue/Green Deployments による MySQL のアップグレードを行なった
  • nginx の ngx_http_proxy_module を活用してサービスのダウンタイムを防止した

はじめに

初めまして。株式会社ジーニーの GENIEE CHAT開発チームのマネージャーを担当しています。
今回は、データベースのメジャーアップグレードを行った際の手順やポイントなどを書いていこうと思います。

移行方針

PostgreSQL

移行前の構成は以下のようになっています。[1]

そして、これが現状の移行後の構成です。

変更点の概要を以下にまとめます。

  • 移行先のRDSインスタンス(PostgreSQL 15) を AWS CDK の新規リソースとして作成
  • AWS Cloud Mapで管理しているデータベース用のレコードを移行先のRDSインスタンスのエンドポイントに変更

Kongは APIゲートウェイ と呼ばれるもので、弊社では認証、ルーティング、チャットの接続先のセッション管理に使用しています。
リクエストは ALB(Application Load Balancer) -> Kong -> その他のマイクロサービス の経路を通るようになっており、リクエストを中継する役割を担っております。
そのため、ダウンした際のサービス影響は中継しているサービス全てになり、非常に広範囲であり、ダウンタイムは許容できません。

また、データベースに対する特性をまとめると以下のようになります。

  • 読み込みは定期的に行われる
  • 書き込みは人間が手動で変更するとき以外、データの変更は発生しない

アップグレードの方法として、インプレースアップグレードを検討しましたが、万が一問題が発生した際に、元のバージョンのPostgreSQLインスタンスをスナップショットから復元するには時間がかかります。
そのため、ブルーグリーンによるデプロイを選択することにしました。

ブルーグリーンによるデプロイ方法の一つとして、RDS Blue/Green Deploymentsを利用する方法があります。
しかし、移行前のバージョンが11.6でありサポートされておらず、使用するには11.21以降に事前にアップグレードしておく必要がありました。
そのため、アップグレード作業を簡潔に行うため、新規のインスタンスの構築を行い、手動で切り替えを行う方針に決定しました。

切り替えに関しては、元々 Kong の接続先として AWS Cloud Map で管理されたCNAMEレコードがあるため、レコードの変更を行い、DNSによる切り替えを行います。

MySQL

移行前の構成は以下のようになっています。

そして、これが現状の移行後の構成です。

移行前と移行後の変更点の概要を以下になります。

  • 移行先のRDSインスタンス(MySQL 8.0.36) を Green 環境として新規作成
  • 移行前のRDSインスタンスは Blue 環境として RDSインスタンスの識別子が変更されて、切り離される

Kongの構成とほぼ同じですが、APIの前段にキャッシュ用のプロキシサーバー(nginx)がある部分が異なります。

管理API は主に二つの用途で使用されていて、管理画面からの設定変更と取得、内部の各種マイクロサービスからの設定取得の二つで使用されています。
こちらもサービスの特性上、ダウンした際のサービス影響は広範囲であり、ダウンタイムは許容できません。

また、データベースに対する特性をまとめると以下のようになります。

  • 読み込みは複数のサービスからAPI経由で行われており、頻繁な読み込みが発生している
  • 書き込みは人間が管理画面経由で設定を変更するとき以外、データの変更は発生しない

アップグレードの方法としてインプレースアップグレードも検討しましたが、こちらも万が一問題が発生した際の復旧時間を抑えるため、ブルーグリーンによるアップグレードを採用しました。
また、こちらは RDS Blue/Green Deployments を利用します。

また、万が一のデータベース移行に失敗した際に、サービス全体が停止することを避けるためプロキシのキャッシュに関して設定変更を行います。(後述)

移行作業の手順

移行作業はそれぞれ以下の手順で行いました。

  1. 開発環境で検証
  2. 移行先のデータベースを作成する
  3. 移行元から移行先に切り替え
  4. 移行元のデータベースを削除

PostgreSQL の移行

開発環境で検証

実際の手順に合わせて、開発環境にて RDS PostgreSQL 15.5 のインスタンスを新規で用意して移行の具体的な手順を検証します。
手順は以下のようになります。

  1. RDS PostgreSQL 15.5の移行先インスタンスを作成
  2. 移行先のインスタンスに psql 経由でログイン後、ユーザーやデータベースの作成を行う。
  3. pg_dump コマンドを使用して移行元のデータベースからデータをダンプする
  4. 2でダンプしたデータを psql 経由で挿入する
  5. Kongの接続先データベースを切り替える
  6. 問題がないか、テストを行う

テストを行う際に、以下の問題が発生しました。

  • 現状Kong側でSSLによる接続に対応していない
  • 現状Kong側でユーザーの認証方式として SCRAM-SHA-256 暗号化に対応していない [2]

これは以下の変更によるものです。

  • PostgreSQL 14 以前では、 rds.force_ssl はデフォルトで無効でしたが、PostgreSQL 15 以降では rds.force_ssl がデフォルトで有効になりました。[3]
  • PostgreSQL 14 以降では password_encryption のデフォルトが md5 から scram-sha-256 に変更されました。

そのため、移行先の RDS のパラメータグループに関して以下2点を変更しました。

  • rds.force_ssl
    • 変更前: 1(デフォルト)
    • 変更後: 0
  • password_encryption
    • 変更前: scram-sha-256(デフォルト)
    • 変更後: md5

移行先のデータベースを作成する

データベースは AWS CDK により構築、管理されているため、以下のようなコードを追加します。(言語はTypeScriptです)

import * as rds from 'aws-cdk-lib/aws-rds';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as cdk from 'aws-cdk-lib/core';

// スタック内に記載
// PostgreSQL 15インスタンスを追加
const postgresEngine15 = rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_15_5 })
const postgres15OptionGroup = new rds.OptionGroup(this, "postgres15-optiongroup", {
  engine: postgresEngine15,
  configurations: [],
});
const postgres15ParameterGroup = new rds.ParameterGroup(this, "postgres15-parametergroup", {
  engine: postgresEngine15,
  parameters: {
    "rds.force_ssl": "0",
    "password_encryption": "md5",
  },
});
const postgres15Instance = new rds.DatabaseInstance(this, "postgres15-instance", {
  engine: postgresEngine15,
  vpc: props.vpc,
  allocatedStorage: 20, // GB
  maxAllocatedStorage: 1000,  // GB
  allowMajorVersionUpgrade: false,
  autoMinorVersionUpgrade: false,
  copyTagsToSnapshot: true,
  backupRetention: cdk.Duration.days(7),
  deleteAutomatedBackups: true,
  // インスタンスタイプを設定 instanceType: new ec2.InstanceType("t3.small"),
  instanceIdentifier: "postgres15",
  multiAz: true,
  optionGroup: postgres15OptionGroup,
  parameterGroup: postgres15ParameterGroup,
  preferredBackupWindow: "18:00-18:30",
  preferredMaintenanceWindow: "sun:19:00-sun:19:30",
  publiclyAccessible: false,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
  // セキュリティグループを設定 securityGroups: [],
  storageEncrypted: true,
  storageType: rds.StorageType.GP3,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
  }
});

// 以下に移行前の PostgreSQL 11インスタンスのコードが続く

移行元から移行先に切り替え

Cloud Mapのレコードを変更して、接続先を切り替えます。

移行元のデータベースを削除

しばらく、サービス稼働が問題ないことを確認した後、AWS CDK で管理されている、移行前の PostgreSQL 11 インスタンスのコードを削除してデプロイします。
これにより古いインスタンスが削除されます。

MySQL の移行

開発環境で検証

実際の手順に合わせて、開発環境に RDS MySQL 8.0.36 のインスタンスを新規で用意して手順の検証を行います。
検証内容は以下のようになります。

  1. レプリケーションの確認
  2. アップグレードの事前チェックの確認
  3. 切り替え中の読み込み、書き込みのダウンタイム確認
  4. アプリケーションからの接続確認とテスト

1. レプリケーションの確認

Green環境作成後に、切り替え前にいくつか確認します。
Green環境で更新が行われた場合、Blue環境に対してレプリケーション経由で書き込みが行われます。
(Blue環境では SHOW MASTER STATUS、Green環境では SHOW SLAVE STATUS で確認することができます。)

データベースに対して書き込み負荷を与えるスクリプトを用意して、レプリケーション経由で書き込みに問題が発生するか SHOW SLAVE STATUS を確認しました。
これにより通常の書き込み負荷に耐えられること、移行後のテーブルに既存のデータが正しく書き込めることを確認しました。

2. アップグレードの事前チェックの確認

Amazon RDSでは、バージョンアップグレード時に事前チェックが行われます。
Green環境のインスタンスの PrePatchCompatibility.log を確認して、問題が発生しているかどうか確認を行いました。
issueは見つかりませんでした。
ログの中身はこのようになっています。

PrePatchCompatibility.log
Executing Compatibility Checks for the MySQL server at /tmp%2Fmysql.sock.
Source Version: 5.7.44-log - Please upgrade to 8.0 or opt-in to the paid RDS Extended Support service before 5.7 reaches end of standard support on 29 February, 2024: https://a.co/hQqiIn0, Target Version: 8.0.36.
1) Usage of old temporal type
No issues found.
2) Usage of db objects with names conflicting with new reserved keywords
No issues found.
3) Usage of utf8mb3 charset
The following objects use the utf8mb3 character set. It is recommended to convert them to use utf8mb4 instead, for improved Unicode support.
More Information:
https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb3.html
mysql - schema's default character set: utf8
4) Table names in the mysql schema conflicting with new tables in 8.0
No issues found.
5) Partitioned tables using engines with non native partitioning
No issues found.
6) Foreign key constraint names longer than 64 characters
No issues found.
7) Usage of obsolete MAXDB sql_mode flag
No issues found.
8) Usage of obsolete sql_mode flags
No issues found.
9) ENUM/SET column definitions containing elements longer than 255 characters
No issues found.
10) Usage of partitioned tables in shared tablespaces
No issues found.
11) Circular directory references in tablespace data file paths
No issues found.
12) Usage of removed functions
No issues found.
13) Usage of removed GROUP BY ASC/DESC syntax
No issues found.
14) Removed system variables for error logging to the system log configuration
No issues found.
15) Removed system variables
No issues found.
16) System variables with new default values
No issues found.
17) Schema inconsistencies resulting from file removal or corruption
No issues found.
18) Issues reported by 'check table x for upgrade' command
No issues found.
19) The definer column for mysql.events cannot be null or blank.
No issues found.
20) Tables with dangling FULLTEXT index reference
No issues found.
21) Routines with deprecated keywords in definition
No issues found.
22) DB instance must have enough free disk space
No issues found.
23) MySQL preupgrade check to catch users created with MYSQL_NATIVE_PASSWORD plugin
No issues found.
24) Creating indexes larger than 767 bytes on tables with redundant row format might cause the tables to be inaccessible.
No issues found.
25) The tables with redundant row format can't have an index larger than 767 bytes.
No issues found.
26) Column definition mismatch between InnoDB Data Dictionary and actual table definition.
No issues found.
Errors: 0
Warnings: 0
Database Objects Affected: 0
----------------------- END OF LOG ----------------------

3. 切り替え中の読み込み、書き込みのダウンタイム確認

Green環境に切り替えを行います。
切り替え中は読み込みと書き込みを1秒間隔で行うスクリプトを用意して、切り替え中に読み込みと書き込みが停止するかを確認しました。

開発環境では、Green 環境切り替え中、約1分ほどの読み込み、約 2 ~ 3分ほどの書き込みが行えなくなることを確認しました。

4. アプリケーションからの接続確認とテスト

この部分で多くの検証を行いました。
結論だけ書くと、発生した問題は 4.2. SQLモードだけでした。

4.1. ユーザーの認証方式

参考記事 : Amazon RDS for MySQL 8.0のデフォルト認証プラグインはmysql_native_password | DevelopersIO

MySQL 8ではデフォルトの認証プラグインは caching_sha2_password に変更されましたが、 Amazon RDS の MySQL 8 ではデフォルトの認証プラグインは mysql_native_password のままになっています。
既存のユーザーの認証プラグインは mysql_native_password になっており、影響を受けることを覚悟していましたが、こちらは影響がありませんでした。

4.2. SQLモード

参考記事 : innodb_strict_mode のセッション値を変更するのに必要な権限(from 8.0.26) - 41から始めました

こちらは切り替え後の接続時にエラーが発生したため早期に気づくことができました。
アプリケーションがデータベース接続後に sql_mode を設定するクエリを実行しており、その中に innodb_strict_mode を設定しているクエリがありました。
MySQL 8.0.26 からは 接続ユーザーが SUPER 権限の場合、 innodb_strict_mode を設定することができなくなります。(公式リンクMySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.26 (2021-07-20, General Availability))

アプリケーション側の sql_mode を修正することでこちらは対応しました。

4.3. 文字コード、照合順序

既存のサービスではデータベース、テーブル、カラム、クライアント単位で utf8mb4 を利用しております。
照合順序(collation) も データベース、テーブル、カラム、クライアント単位で設定されているため影響を受けませんでした。
この辺りはサービス開始時から意識しておいて良かったポイントだなと思いました。

4.4 クエリキャッシュ

使用していなかったため、影響を受けませんでした。

4.5 GROUP BY 句での ASC/DESC 修飾子の使用

GROUP BY を使用している箇所を確認しつつ、一般ログ(general_log) とアプリケーションのログから確認しました。
こちらも問題はありませんでした。

移行先のデータベースを作成する

既存のRDS MySQL 5.7.44 から Blue/Green Deploymentsを利用して RDS MySQL 8.0.36 のGreen環境を作成します。
Green環境の作成中は、Blue環境に対して Read/Write は問題なく行えるため、本番環境でもGreen環境をデプロイ作業前にあらかじめ作成しておくことは可能です。
途中エンジンのアップグレードなども行われるため、自分の開発環境では 30 ~ 1時間ほどかかりました。

プロキシキャッシュの変更を有効化

参考記事 : Module ngx_http_proxy_module

切り替え直前に、 管理API の プロキシキャッシュとして使用している nginx の 設定を変更します。
切り替えを行う前に、proxy_cache_use_stale updating に設定します。
これにより万が一オリジンサーバーが停止していて、キャッシュが stale でもレスポンスを返すことができるため、サービスは継続して稼働することができます。

移行元から移行先に切り替え

AWSコンソール上から切り替えを実行します。
実際の切り替えは本番環境も同様に約3分ほどで完了しました。

切り替え後に切り替え前と同様に nginx の設定を元に戻して反映させます。

移行元のデータベースを削除

しばらく、サービス稼働が問題ないことを確認した後、AWSコンソール上からBlue環境のRDSインスタンスを手動で削除しました。

気づき

以下に私の気づきを記載します。

  • サーバー側での問題検出はRDS側である程度担保してくれる
    • パラメータグループを移行前と移行後で同じものを作った後、変更点をリストアップする
    • PrePatchCompatibility.log はチェックしておこう
  • クライアント側原因の問題検出は入念な開発環境での検証が必要
    • 接続元のサービスの一覧とアクセスするタイミング、頻度を確かめておく(大量の書き込み処理が行われるバッチ処理の実行タイミングを避けるなど)
  • 確認項目のリストアップと切り替え作業の手順化(ロールバックも含む)をしておくと精神的に落ち着いて作業がしやすい
    • 問題発生時のパニックを避ける
  • changelogを見る作業は大変だが、一つずつ問題を確認できるので遠回りのように見えて確実である

まとめ

GENIEE CHAT の サービスが始まって以来、初めてのデータベースのメジャーアップグレード作業ということで非常に緊張しましたが、入念な事前検証と細かいchangelogの確認のおかげで無事問題なく切り替えることがでいました。

今回のアップグレード作業対象は5台ありましたが、一部の作業をメンバーに作業を委譲しました。
そうすることでチーム全体で知見共有ができたことも非常に良かったと感じました。

参考にした資料

以下に参考にした記事のリンクを載せておきます。
この場を借りてお礼申し上げます。

PostgreSQL

MySQL

脚注
  1. 実際のサービス名称やDNSのレコード名とは異なります。説明用に簡潔なものにしています。 ↩︎

  2. Kongが使用している pgmoon が v1.13.0 以降であれば対応しています。 https://github.com/leafo/pgmoon/releases/tag/v1.13.0 ↩︎

  3. https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/PostgreSQL.Concepts.General.SSL.html#PostgreSQL.Concepts.General.SSL.Requiring ↩︎

GENIEE TechBlog

Discussion