Skip to content

Aurora(PostgreSQL) 13→16 Blue/Green アップグレード(本番実施版)

作成日: 2026-06-19 担当: SRE(yusaku.ishizawa) 関連ドキュメント:


📌 本書は「共通手順(how)」です。 クラスタ名・インスタンス構成・拡張・target version・確認テーブル名などのサービス固有の具体値は サービス別パラメータ を正とします。

  • PG16 アップグレード全体の索引・進捗: README
  • 本書中のコマンドは eligibility-verificationワークド例として記載しています。別サービスで実施する場合は、該当する services/<service>.md の値に読み替えてください。

本書について

  • Aurora PostgreSQL を Blue/Green Deployment で 13系 → 16系 にメジャーアップグレードする手順のうち、本番(production)の実クラスタ本体に対する本番手順を記載する。
  • Clone リハーサル版が「破棄可能な複製での素振り」だったのに対し、本書は 実トラフィックのある本番クラスタを対象とし、**メンテナンス枠・関係者周知・監視・アプリ再接続・Terraform 整合(reconcile)**を含む。
  • 対象サービスは services/ 配下の各サービス(本書のワークド例は eligibility-verification(production))。実施前に対象サービスの services/<service>.md のパラメータを確認すること。
  • DB 側の操作手順そのもの(パラメータグループ・logical replication・Blue/Green 作成・Switchover・拡張更新・性能確認)はリハーサル版で実証済み。本書はリハーサル版との差分(実クラスタ向けの段取り・注意・Terraform 整合)を中心に、本番として通しで実行できる形にまとめる。各手順の細かい SQL/CLI の背景説明はリハーサル版を参照のこと。
  • ⚠️ Clone は作成しない(本書は実クラスタ eligibility-verification 本体を直接アップグレードする)。

リハーサル版との主な差分(最初に読む)

観点リハーサル版(Clone)本書(本番・実クラスタ)
対象破棄可能な Clone本番クラスタ eligibility-verification 本体
Clone 作成あり(手順1)なし(スキップ)
custom CPG の作成staging で作成済み(#2164)本番には未作成 → Terraform で追加・apply が必要(フェーズA)
CPG 付け替え+再起動Clone に対して(無影響)本番 DB に瞬断発生 → 事前メンテ枠で実施(フェーズB)
ベースライン取得無トラフィックの素振り実トラフィックのある本番で事前取得(フェーズA)
周知・監視 mute・DDL 凍結不要必須
Switchover任意のタイミングメンテ枠・低トラフィック帯・アプリ再接続/主要機能確認
Terraform 整合不要(Clone は破棄)必須(rds_engine_version・family を 16 系へ。未実施だとドリフトで replace 危険)
ロールバックClone 削除のみSwitchover 前=BG削除で中止 / 後=切り戻し困難(fix-forward 優先)
後始末全リソース削除新 PG16 を残し、旧 PG13(-old1)は数日安定確認後に削除

関連 Issue / PR

  • 親チケット: mental-online-karte #12725 「[DB] eligibility-verification PostgreSQL 16系アップグレード」
  • 本番手順書 Issue: mental-online-karte #13721 「[DB][eligibility-verification] 既存 Aurora の PG16 Blue/Green 本番手順書を docs に追加」
  • staging 調査・検証の進捗: mental-online-karte #13098
  • Clone リハーサル(素振り)Issue: mental-online-karte #13719
  • 実装メモ(カスタム PG / logical_replication / PG16 ターゲット): mental-online-karte #12905
  • カスタム PG / logical_replication が必要な DB の棚卸し・方針決定: mental-online-karte #13044
  • PG16 用 custom parameter group の追加(staging・MERGED): terraform_for_aws #2164(本番版はこの差分を production に展開する

前提

  • DB 側の手順は Clone リハーサル版 で実証済みであること。
  • 本番 Terraform に書き込み apply できる権限があること(⚠️ production の read-only プロファイルでは CPG 作成 apply / modify-db-cluster / reboot-db-instance ができない。書き込み権限の手当てが前提。#12905 参照)。
  • 踏み台(bastion)から SSM ポートフォワードで本番 Aurora に接続できること。サービス別の踏み台 ID は services/<service>.md の「環境・接続」表に記載(例: eligibility-verification の本番踏み台は i-03b8c9b9fb3c4fe9a(fd-office-connection-bastion)。fd-platform 踏み台は EVS SG 不許可で不可)。
  • DB 接続情報は Secrets Manager(eligibility-verification)から取得し、パスワードはコマンド/出力に出さず PGPASSWORD 経由で扱う。
  • Aurora の logical replication(cluster パラメータ)反映には 対象インスタンスの再起動が必須(static パラメータ)。
  • アップグレード前 必須確認(PG16 非互換・拡張・接続棚卸し等)は プレフライト チェックリスト を対象サービスで実施済みであること。

影響

  • 本番 DB に瞬断が発生する箇所が2つある:
    1. フェーズB(CPG 付け替え+再起動): logical replication 有効化のため Writer(および Reader)を再起動 → 数十秒〜数分の接続断
    2. フェーズD(Switchover): 書き込み断は短時間(リハーサル実測 約2秒)だが、再接続・DNS 伝播の影響あり。
  • Blue/Green 作成・Green 検証・ANALYZE(フェーズC)は Blue(本番)にダウンタイムを発生させない(Green は別環境)。ただし論理レプリケーション開始に伴う負荷・lag は監視対象。
  • いずれの瞬断も メンテナンス枠・低トラフィック帯で実施し、Feature チーム周知・監視 mute を行う。

本番特有の必須事項(チェック)

  • [ ] Feature チーム・関係者へ作業日時・影響(2回の瞬断)を事前周知
  • [ ] メンテナンス枠を2つ確保(①CPG 付け替え+再起動、②Switchover。同一枠に詰めず分けることを推奨)
  • [ ] 監視アラートの mute / 抑制設定(誤検知防止)
  • [ ] DNS キャッシュ TTL を 5 秒以下に(アプリ・コネクションプーラ・OS リゾルバ・JVM networkaddress.cache.ttl 等)
  • [ ] DDL / migration / 大量更新バッチの凍結計画(フェーズC 同期中は Blue へ DDL を流さない)
  • [ ] ロールバック判断者・監視担当者の待機
  • [ ] 書き込み権限のある AWS プロファイルの手当て

全体の流れ(フェーズ構成)

フェーズA 事前準備(メンテ枠外・日中可)
  - Terraform で本番用 CPG(PG13 custom + PG16 target)を追加・apply
  - プレフライト確認 / pg_stat_statements ベースライン取得(実トラフィック)
  - 周知 / DDL・migration 凍結計画 / DNS TTL 確認 / 監視準備
フェーズB 事前メンテ枠①(瞬断あり)
  - custom CPG を本番クラスタへ付け替え → 再起動(Writer→各Reader 1台ずつ)→ in-sync
  - logical_replication=on 確認 / pg_stat_statements 拡張を作成
フェーズC Green 準備(メンテ枠外・Blue は無停止)
  - Blue/Green 作成前チェック
  - Blue/Green 作成(target=PG16/16.13)→ AVAILABLE
  - Green 検証(read-only / ANALYZE)
  - Blue への DDL/migration 凍結(同期中)
フェーズD メンテ枠②(Switchover・低トラフィック帯)
  - Switchover 前スナップショット(PG13)取得 → available
  - 最終ゲート確認 → Switchover → 完了確認
  - PG16 疎通・アプリ再接続/主要機能確認 / ALTER EXTENSION UPDATE / slot 確認
フェーズE 事後(数日)
  - 性能監視(実トラフィック最低3日 / ベースライン比較)
  - Terraform 整合(rds_engine_version→16, family→aurora-postgresql16)★ドリフト防止
  - 後始末(旧 PG13 -old1 を数日安定後に削除)

共通の環境変数

各ターミナルの先頭で設定する。本番は Clone を作らないため、操作対象は一貫して本体 $SOURCE_CL具体値(プロファイル・クラスタ名・BG 名・target version 等)は対象サービスの services/<service>.md を正とする。下記は eligibility-verification のワークド例。

bash
P=<書き込み権限のある production プロファイ>   # ⚠️ read-only プロファイル不可(services/<service>.md 参照)
R=ap-northeast-1

# アップグレード対象=本番クラスタ本体(Clone は作らない)。値は services/<service>.md の「クラスタ識別子」
SOURCE_CL=eligibility-verification
CL=$SOURCE_CL          # リハーサル版コマンドの $CL をそのまま流用できるよう別名を揃える

# Blue/Green 名・スナップショット名(services/<service>.md の「Blue/Green 名」)
BG_NAME=eligibility-verification-bg

⚠️ 事故防止: 本書のコマンドはすべて本番本体(eligibility-verification)を対象にする。Switchover 後は名前がスワップし $SOURCE_CL新 PG16 を指す(旧 PG13 は -old1)。スナップショット復元など「PG13 を対象にしたい」操作で $SOURCE_CL を誤って指定しないこと(→ ロールバック/付録)。


手順

フェーズA. 事前準備(メンテ枠外・日中に実施可)

A-1. Terraform で本番用 parameter group を追加・apply(★本番未作成)

本番 eligibility-verification/production には custom CPG / PG16 ターゲット PG がまだ無い。staging の PR #2164 と同じ差分を production に展開する。

  1. fastdoctor-template/eligibility-verification/production/main.tfmodule "microservice-ecs" の DB 節に、ソース側 custom CPG(PG13)を追加:
hcl
  # DB
  rds_username               = var.DB_USERNAME
  rds_password               = var.DB_PASSWORD
  rds_database_name          = var.DB_NAME
  rds_engine_version         = "13.20"
  rds_cluster_instance_count = var.rds_cluster_instance_count

  # Blue/Green(PG16) 前提: 論理レプリケーション有効化のため custom Cluster PG を作成・付与
  rds_family                            = "aurora-postgresql13" # 既定と同値だが明示
  cluster_parameter_group_custom_enable = true
  cluster_parameter_group_name          = var.project           # = "eligibility-verification"
  cluster_parameter_group_params = {
    "rds.logical_replication"  = { value = "1", apply_method = "pending-reboot" }
    "wal_sender_timeout"       = { value = "0", apply_method = "pending-reboot" }
    # ⚠️ shared_preload_libraries は上書き型。source の既存値を落とさず pg_stat_statements を「追加」する。
    #    本番の現値を A-1 前に確認し、必要なら "rdsutils,pg_stat_statements" 等にする(下記注記)。
    "shared_preload_libraries" = { value = "pg_stat_statements", apply_method = "pending-reboot" }
  }
  1. PG16 ターゲット用 Cluster/Instance PG(standalone)を追加(staging の pg16_param_groups.tf 相当。template_modules/options/aurora-bluegreen-param-groups を利用)。名前は eligibility-verification-pg16、family は aurora-postgresql16、パラメータは PG13 側と同値(rds.logical_replication=1 / wal_sender_timeout=0 / shared_preload_libraries)。

⚠️ shared_preload_libraries の保全: apply 前に本番 source の現値を必ず確認し、rdsutils や(サービスにより)pgaudit/pg_cron 等を落とさず pg_stat_statements を追加する。PG16 ターゲット CPG にも同じ値を揃える(preload が要る拡張は揃えないと Switchover 後に再有効化できない)。 ⚠️ default.aurora-postgresql13 を見ないこと。source が custom CPG を使っているサービスでは誤る。現在 source cluster に紐づく CPG を取得して確認する:

bash
SOURCE_CPG=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].DBClusterParameterGroup" --output text --region $R --profile $P)
aws rds describe-db-cluster-parameters --db-cluster-parameter-group-name "$SOURCE_CPG" \
  --query "Parameters[?ParameterName=='shared_preload_libraries'].[ParameterName,ParameterValue,ApplyMethod,Source]" \
  --output table --region $R --profile $P

eligibility-verification の実機確認(2026-06): 拡張は plpgsql のみ、pg_cron/pg_partman/pg_repack 未使用、Reader 無し(Writer 1台)。preload は pg_stat_statements の追加で足りる見込みだが、本番 source の現値で最終確認すること。

  1. plan 期待値(誤操作検知。-target=module.microservice-ecs 等で対象を絞ると安全):

    • aws_rds_cluster_parameter_group.default[0](custom CPG, PG13)… 1 to add
    • PG16 用 Cluster PG / Instance PG(eligibility-verification-pg16)… 2 to add
    • aws_rds_cluster変更なしdb_cluster_parameter_group_nameignore_changesこの apply では CPG が作られるだけで live cluster には付かない=付け替えはフェーズB で CLI 実施)
    • ⚠️ cluster の置換(replace) や instance 再作成が出ないことを必ず確認してから apply
  2. apply 後、両 CPG の設定を確認:

bash
for PG in eligibility-verification eligibility-verification-pg16; do
  echo "== $PG =="
  aws rds describe-db-cluster-parameters --db-cluster-parameter-group-name $PG \
    --query "Parameters[?ParameterName=='rds.logical_replication'||ParameterName=='wal_sender_timeout'||ParameterName=='shared_preload_libraries'].[ParameterName,ParameterValue,ApplyMethod]" \
    --output table --region $R --profile $P
done

期待値(両方): rds.logical_replication=1 / wal_sender_timeout=0 / shared_preload_librariespg_stat_statements を含む(いずれも pending-reboot)。

A-2. プレフライト確認・Blue/Green 可否

プレフライト チェックリスト を実施のうえ、本体が BG 可能な状態かを確認(リハーサル版 手順0-2〜0-5 と同一):

bash
# Status=available / Backup>0 / Engine=13.20 / Members(台数を控える=再起動対象数)
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL --region $R --profile $P \
  --query "DBClusters[0].{Status:Status,Backup:BackupRetentionPeriod,Engine:EngineVersion,Members:DBClusterMembers[].DBInstanceIdentifier}" --output json

# Global Database 非所属(何も出ないこと)。※ ARN で厳密判定(部分一致を避ける)
SOURCE_ARN=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].DBClusterArn" --output text --region $R --profile $P)
aws rds describe-global-clusters --region $R --profile $P \
  --query "GlobalClusters[?contains(GlobalClusterMembers[].DBClusterArn, '$SOURCE_ARN')].[GlobalClusterIdentifier]" --output table

# 既存 BG / RDS Proxy が無いこと
aws rds describe-blue-green-deployments --region $R --profile $P \
  --query "BlueGreenDeployments[?contains(Source,'$SOURCE_CL')].{Name:BlueGreenDeploymentName,Status:Status}" --output table

# target version 16.13 がアップグレード先に含まれること
aws rds describe-db-engine-versions --engine aurora-postgresql --engine-version 13.20 --include-all --region $R --profile $P \
  --query "DBEngineVersions[0].ValidUpgradeTarget[?starts_with(EngineVersion,'16')].EngineVersion" --output text

⚠️ Members の台数を控える。Reader がいれば フェーズB の再起動対象が増える(Writer→各 Reader を1台ずつ)。eligibility は Writer 1台想定だが本番実機で必ず確認する。

A-3. pg_stat_statements ベースライン取得(実トラフィックのある本番で)

アップグレード後は pg_stat_statements の Query ID が変わるため、事前に本番(実トラフィック)でベースライン(負荷の高い SQL 一覧)を取得し、フェーズE で突き合わせる。/tmp/pg_stat_baseline.csv を取得し、できれば数日分の傾向を把握する。

⚠️ 取得順序(拡張の有無で分岐): 下記 SQL は pg_stat_statements 拡張が作成済みでないと relation "pg_stat_statements" does not exist で失敗する。まず確認:

sql
SELECT installed_version FROM pg_available_extensions WHERE name='pg_stat_statements';  -- NULL なら未作成
  • A-3a(既に作成済みの場合): そのまま下記でベースライン取得。
  • A-3b(未作成の場合・eligibility 等): 拡張作成はフェーズB-5(CREATE EXTENSION)。作成直後は統計が空なので、B-5 後に通常トラフィックを一定期間(最低数時間〜数日)流してからベースライン取得 → その後フェーズC/D へ進む。「CREATE 直後に即取得」はしない。
    • 順序: B-5 で CREATE通常トラフィック蓄積ベースライン取得C/D
    • ⚠️ shared_preload_libraries への追加+拡張作成のため、B-5 は再起動(フェーズB)後=この時点で初めて取得可能。日程に余裕を持たせる。

⚠️ カラム名はバージョンで異なる: Aurora PG13(本書の source)は total_exec_time / mean_exec_time。RDS PG12(例: fastdoctor-manager-db*)が source の場合は total_time / mean_time に読み替える(PG13 で改名)。

sql
-- Aurora PG13(source): 現DBに限定して Top SQL を取得
SELECT queryid, LEFT(query,100) AS query_preview, calls,
       round(total_exec_time::numeric,2) AS total_ms, round(mean_exec_time::numeric,2) AS mean_ms, rows
FROM pg_stat_statements
WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
ORDER BY total_exec_time DESC LIMIT 50;

-- CSV エクスポート(事後 E-1 で比較)
\copy (SELECT queryid, query, calls, total_exec_time, mean_exec_time, rows FROM pg_stat_statements WHERE dbid=(SELECT oid FROM pg_database WHERE datname=current_database()) ORDER BY total_exec_time DESC LIMIT 50) TO '/tmp/pg_stat_baseline.csv' CSV HEADER;

加えて 主要クエリの EXPLAIN(実行計画)も事前保存する(事後 E-1 で比較)。

sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ... ;  -- ベースライン上位の主要クエリを順に

⚠️ EXPLAIN ANALYZE実際にクエリを実行する。更新系は BEGIN; ... ROLLBACK; で囲むか参照系のみに留める。

本番は実トラフィックがあるため、ベースラインは「アップグレード前の通常負荷の実態」を表す。フェーズE の性能判断の基準になる。

A-4. 周知・凍結計画・DNS TTL・監視準備

  • Feature チーム・関係者へ周知(2回の瞬断=フェーズB 再起動/フェーズD Switchover の日時・影響)。
    • タイミング: 初回告知は 5営業日前(≒1週間前)、リマインドを 前日当日(作業開始 30〜60分前)枠①(再起動)/枠②(Switchover)が別日になる場合は枠ごとに当日リマインドし、各枠の完了直後に「完了しました」と報告する。「DDL/migration 凍結」の依頼を相手のデプロイ計画に織り込んでもらうため、直前のみの告知は避ける。
    • 報告チャネル: #on本部_release-ops(リリース/メンテ作業の告知)+ #fdtech-general(技術メンバー全体への周知)。両チャネルに告知・リマインド・完了報告を投稿する。
    • 文言テンプレ(例) — 各告知/リマインドで投稿し、各枠の完了後に「完了しました」と返信:

      【本番メンテ作業のお知らせ】 Aurora(eligibility-verification / production)に PG16 アップグレードの作業を行うため、下記2回お時間をいただきます。 ・枠①(再起動): MM/DD(曜) HH:MM〜 約〇〇分(数分の接続瞬断) ・枠②(Switchover): MM/DD(曜) HH:MM〜 約〇〇分(書込断 数秒) 作業中は一時的な接続断・書込断が発生します。その間は DDL(migration など)に関するデプロイ・大量更新・バッチ実行を控えてください。 完了後に本チャンネルで報告します。連絡先: @担当 / #on本部_release-ops

  • 外部 DB クライアントの棚卸し(A-0): Trocco / ReTool / ReDash / BI / バッチ / Airflow 等が対象 DB を直接参照していないかを確認(SG inbound / Secrets・IaC / 各ツール管理画面)。instance endpoint・IP 直指定のものは接続先修正が必須。管理しきれない接続は Switchover 後に Blue(-old1)側の pg_stat_activity / full query log(log_connections / pgaudit)で実地に洗い出す(→ フェーズD 事後)。
  • DDL / migration / 大量更新バッチ・pg_cron 等の凍結計画(フェーズC の BG 同期中は Blue へ DDL を流さない)。
  • DNS キャッシュ TTL を 5 秒以下に(アプリ・コネクションプーラ・OS・JVM)。長いと Switchover 後も旧 Blue(-old1)へ書き込みが続くリスク。
  • 監視 mute / ロールバック判断者・監視担当者の待機を段取り。
  • default_transaction_read_only をアプリが off に上書きしていないかをコード/ORM/初期化 SQL で監査(リハーサル版 手順4-2。切替中のデータ混入防止)。

フェーズB. 事前メンテ枠①(custom CPG 付け替え+再起動=瞬断あり)

⚠️ これは Switchover(フェーズD・メンテ枠②)とは別メンテ枠で実施(必須)。同一枠に詰めない。

  • 理由: 再起動(B) と Switchover(D) の間に Green 作成(フェーズC・約31分・無停止)が必須で、その Green 作成は logical replication 有効化済み(=この再起動が完了している)ことが前提。よって B→C→D は時系列が離れ、B と D は必ず別タイミングにする(1時間枠で D を回すための前提でもある)。
  • ダウンタイム: Aurora インスタンスの再起動による瞬断(1台あたり数十秒〜数分。Reader がいれば Reader→Writer の順で1台ずつ=枠は台数分必要)。Switchover の書込断(数秒)とは別物。
  • 実施条件: 低トラフィック帯・監視 mute・関係者周知のうえ実施。
  • ⚠️ source が既に rds.logical_replication=on(例: Datastream 運用サービス)なら本フェーズB(再起動)は不要=ダウンタイムは Switchover の1回だけになる。default CPG(logical off)のサービス(例: eligibility)はこの再起動枠が必要。

B-1. custom CPG を本番クラスタへ付け替え

db_cluster_parameter_group_name は Terraform の ignore_changes 対象(A-1 の apply では付かない)。CLI で付け替える:

bash
aws rds modify-db-cluster \
  --db-cluster-identifier $SOURCE_CL \
  --db-cluster-parameter-group-name eligibility-verification \
  --apply-immediately --region $R --profile $P

B-2. 反映状態を確認(再起動前は pending-reboot

bash
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query 'DBClusters[0].DBClusterMembers[].{Instance:DBInstanceIdentifier,Writer:IsClusterWriter,PGStatus:DBClusterParameterGroupStatus}' \
  --output table --region $R --profile $P

全メンバーが pending-reboot であることを確認。

B-3. 再起動(Reader → Writer の順)

⚠️ Reader がいる構成では同時に全部落とすと全断。順序は Reader を先に1台ずつ → 最後に Writer(前のインスタンスが available になってから次へ)。eligibility は Writer 1台想定。 ⚠️ Writer を先に再起動しないこと。Writer 再起動で Failover が起きると、まだパラメータ未反映の Reader が新 Writer に昇格して反映漏れが残る。先に Reader を反映させ最後に Writer を再起動すれば、Failover で昇格しても昇格先 Reader は反映済み。 ⚠️ インスタンス名を固定しない${SOURCE_CL}-0 決め打ちは誤り)。Writer/Reader は AWS API から取得する(本書は共通手順のため)。

bash
# Reader を API から列挙し、先に1台ずつ reboot → wait(同時に落とすと全断)
READER_IDS=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].DBClusterMembers[?IsClusterWriter==\`false\`].DBInstanceIdentifier[]" \
  --output text --region $R --profile $P)
for RID in $READER_IDS; do
  aws rds reboot-db-instance --db-instance-identifier "$RID" --region $R --profile $P
  aws rds wait db-instance-available --db-instance-identifier "$RID" --region $R --profile $P
done

# 最後に Writer を再起動(Failover しても昇格先 Reader は反映済み)
WRITER_ID=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].DBClusterMembers[?IsClusterWriter==\`true\`].DBInstanceIdentifier | [0]" \
  --output text --region $R --profile $P)
aws rds reboot-db-instance --db-instance-identifier "$WRITER_ID" --region $R --profile $P
aws rds wait db-instance-available --db-instance-identifier "$WRITER_ID" --region $R --profile $P

B-4. 再起動後 in-sync を確認

bash
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query 'DBClusters[0].DBClusterMembers[].{Instance:DBInstanceIdentifier,PGStatus:DBClusterParameterGroupStatus}' \
  --output table --region $R --profile $P

全メンバー in-sync

B-5. 踏み台接続 → logical replication 確認 → pg_stat_statements 拡張作成

リハーサル版 手順3-1〜3-4 と同一。SSM ポートフォワード → psql 接続(Secrets Manager / PGPASSWORD)後:

sql
SHOW rds.logical_replication;  -- on
SHOW wal_level;                -- logical
SHOW wal_sender_timeout;       -- 0

-- BG 作成前(source が writable なうち)に有効化
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
\dx pg_stat_statements

3つの値が on / logical / 0 で揃わなければ Blue/Green の論理レプリケーションが成立しない(フェーズC に進まない)。

A-3 のベースラインをこの段階でまだ取っていなければ、ここで取得しておく(実トラフィックのある本番 source で)。


フェーズC. Green 準備(メンテ枠外・Blue は無停止)

Blue/Green 作成・Green 検証は 本番 Blue にダウンタイムを発生させない(Green は別環境)。Switchover 当日のメンテ枠の“前”に完了させておく(Green 作成はリハーサル実測 約33分。本番はデータ・負荷で変動)。

C-1. Blue/Green 作成前チェック

リハーサル版 手順4 と同一(Status=available / Backup>0 / Global=None / custom CPG が全メンバー in-sync / PG16 用 CPG・Instance PG が存在 / 長時間トランザクション無し)。論理レプリケーション制限事項(手順4-1)・default_transaction_read_only 監査(手順4-2)・CloudWatch アクティビティ(手順4-3)も確認。

C-2. Blue/Green 作成(target=PG16 / 16.13)

bash
SRC_ARN=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query 'DBClusters[0].DBClusterArn' --output text --region $R --profile $P)

aws rds create-blue-green-deployment \
  --blue-green-deployment-name $BG_NAME \
  --source "$SRC_ARN" \
  --target-engine-version 16.13 \
  --target-db-cluster-parameter-group-name eligibility-verification-pg16 \
  --target-db-parameter-group-name eligibility-verification-pg16 \
  --region $R --profile $P

返ってきた BlueGreenDeploymentIdentifierbgd-xxxx)を控える:

bash
BG=bgd-xxxxxxxxxxxxxxxx

PROVISIONINGAVAILABLE まで待機(リハーサル版 手順5。describe-blue-green-deployments / Green の describe-events で進捗確認)。期待: Status=AVAILABLE / CREATING_READ_REPLICA_OF_SOURCE=COMPLETED / DB_ENGINE_VERSION_UPGRADE=COMPLETED。アップグレード中に Green だけがオフラインになる(Blue=本番は稼働継続=ユーザー影響なし)。

C-3. Green 検証(read-only / ANALYZE)

リハーサル版 手順6 と同一。Green の Writer エンドポイントへ別ポート(例 15432)でフォワードし:

sql
SHOW server_version;            -- 16.13
SHOW rds.logical_replication;   -- on
SHOW shared_preload_libraries;  -- pg_stat_statements を含む(+ writeforward / rds_blue_green は AWS 自動付与)

-- Blue とのデータ整合(主要テーブルの件数)
SELECT COUNT(*) FROM _prisma_migrations;
SELECT COUNT(*) FROM ocr_results;
SELECT COUNT(*) FROM prompt_definitions;

-- ★Switchover 前に Green で統計を作る(切替直後の性能事故を防ぐ。Blue=本番は無負荷)
ANALYZE VERBOSE;

Green は Switchover まで業務データへの書き込みは禁止。Switchover 前に Green で行う操作は read 確認と ANALYZE に限定する(ANALYZE は業務データを変えないが統計は更新するため、厳密には「read-only」ではない)。ALTER EXTENSION 等の DDL は Switchover 後に実施。

C-4. Blue への DDL/migration 凍結(同期中)

BG 同期中(C-2〜D まで)は Blue(本番)へ DDL / migration / 大量更新を流さない(論理レプリケーション制限の回避)。デプロイパイプライン・migration・pg_cron 等を凍結状態にしておく。BG 全体に Replication degraded が出ていないこと(StatusDetails=null)も確認(手順6-5)。


フェーズD. メンテ枠②(Switchover・低トラフィック帯)

低トラフィック帯・メンテ枠で実施。書き込み断は短時間(実測 約2秒)だが、再接続・DNS 伝播の影響を最小化する。1時間枠なら「Switchover と事後確認に集中」し、Green 作成・スナップショットは枠の前に終わらせる(→ 末尾「メンテ枠の時間設計」)。

D-0. Switchover 前スナップショット(PG13)取得 → available(本番必須)

ロールバック用に、Switchover の前に Blue=PG13($SOURCE_CL、この時点では PG13)を手動スナップショット取得する。Switchover 後は名前がスワップするため、PG13 を確実に確保するには必ず Switchover 前に取得する。

bash
SNAP=${SOURCE_CL}-pre-pg16-$(date -u +%Y%m%d%H%M)
aws rds create-db-cluster-snapshot \
  --db-cluster-snapshot-identifier "$SNAP" \
  --db-cluster-identifier "$SOURCE_CL" \
  --region $R --profile $P
aws rds wait db-cluster-snapshot-available --db-cluster-snapshot-identifier "$SNAP" --region $R --profile $P
# エンジンが 13.x であること(=Blue=PG13 を取得できている)を確認
aws rds describe-db-cluster-snapshots --db-cluster-snapshot-identifier "$SNAP" \
  --query "DBClusterSnapshots[0].{Snap:DBClusterSnapshotIdentifier,Engine:EngineVersion,Status:Status,Created:SnapshotCreateTime}" \
  --output table --region $R --profile $P

⚠️ スナップショットは Switchover 直前に“開始”しない(available 待ちで枠を食い潰す)。枠の前〜早い段階で取得開始 → available 確認してから Switchover。スナップショット名・取得時刻・エンジンバージョンを Issue に記録(#12725 9-1)。復元手順はリハーサル版「付録: スナップショットからの復元」。

D-1. 最終ゲート確認

bash
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
  --query "BlueGreenDeployments[0].{Status:Status,StatusDetails:StatusDetails,Sw:SwitchoverDetails[].Status}" \
  --output json --region $R --profile $P

期待: Status=AVAILABLE / StatusDetails=null / Sw=["AVAILABLE","AVAILABLE"]

GO/NO-GO チェック(全てクリアで Switchover 実行・1つでも NG なら延期):

  • [ ] BG Status = AVAILABLE
  • [ ] StatusDetails = null(Replication degraded なし)
  • [ ] SwitchoverDetails 各メンバー = AVAILABLE
  • [ ] replica lag = 0 付近(Green CloudWatch AuroraReplicaLag / OldestReplicationSlotLag
  • [ ] 長時間トランザクション = 0(pg_stat_activity
  • [ ] DDL / migration 凍結済み
  • [ ] PG13 スナップショット(D-0)取得済み・available
  • [ ] 監視 mute 済み
  • [ ] DNS キャッシュ TTL ≤ 5秒
  • [ ] default_transaction_read_only をアプリが off 上書きしていない(C 監査済み)
  • [ ] ロールバック判断者・監視担当・アプリ主要担当が待機
  • [ ] Terraform PG16 整合 PR 準備済み(E-2・Switchover 後すぐ plan/apply できる状態)

D-2. Switchover 実行

bash
aws rds switchover-blue-green-deployment --blue-green-deployment-identifier $BG \
  --switchover-timeout 300 --region $R --profile $P

--switchover-timeout(秒・既定 300)は lag=0 を待つ最大時間。超過すると Switchover は失敗扱いになり両環境に変更を加えず自動ロールバックされる。本番の書き込み量が多い時間帯は値を見直す。 切替で 名前がスワップ(Green が $SOURCE_CL の名前を取得、旧 source は -old1)。エンドポイント名は据え置きのため、アプリの接続先設定は変更不要(接続は一瞬切断 → 自動再接続)。

D-3. 完了確認

bash
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
  --query "BlueGreenDeployments[0].Status" --output text --region $R --profile $P
# SWITCHOVER_COMPLETED
aws rds describe-events --source-type db-cluster --source-identifier $SOURCE_CL \
  --duration 60 --region $R --profile $P --query "Events[].[Date,Message]" --output table
# "Renamed <blue> to <blue>-old1 and <green> to <blue>" を確認

D-4. PG16 疎通・アプリ再接続/主要機能確認

切替後は元の名前 $SOURCE_CL が新 PG16 を指す。SSM ポートフォワード → psql で:

sql
SHOW server_version;   -- 16.x
SELECT current_database(), current_user, inet_server_addr(), inet_server_port();
ANALYZE VERBOSE;       -- 主要テーブルの last_analyze が空なら再実行

アプリ側の再接続・主要機能を必ず確認(本番固有):

  • ECS タスクが新 DB へ再接続できている(接続エラーが継続しない/DatabaseConnections が回復)
  • 主要 API・OCR の read/write が成功する
  • ALB ヘルスチェック healthy / 5xx・APM error が収束

外部 DB クライアントの残留確認(A-0 の事後確認):

  • 旧 Blue(-old1)側に残っている接続を確認し、切替が必要な外部ツールを実地に洗い出す(A-0 で管理しきれなかった接続の補足)。
sql
-- 旧 Blue(-old1)に接続して、まだ繋いでいるクライアントを列挙(アプリ以外=要切替候補)
SELECT usename, application_name, client_addr, count(*) AS conns, max(now()-backend_start) AS age
FROM pg_stat_activity WHERE pid<>pg_backend_pid() AND client_addr IS NOT NULL
GROUP BY usename, application_name, client_addr ORDER BY conns DESC;
  • 必要に応じ log_connections / pgaudit の full query log も参照。Trocco / ReTool / ReDash / バッチ等が -old1 に残っていたら接続先を新エンドポイントへ修正(instance endpoint・IP 直指定は特に要注意)。

DNS キャッシュが長いと旧 Blue(-old1)へ書き込みが続くリスク。再接続が確認できない場合は DNS TTL / コネクションプール設定を点検。

D-5. 拡張更新・slot 確認(writable になったので実行可)

sql
ALTER EXTENSION pg_stat_statements UPDATE;          -- Version が 1.10 へ
\dx pg_stat_statements

-- 更新が必要な拡張が残っていないか(0 行が正)
SELECT name, installed_version, default_version FROM pg_available_extensions
  WHERE installed_version IS NOT NULL AND installed_version <> default_version ORDER BY name;

-- BG 用 slot の解消確認(出力はサービス依存・下記参照)
SELECT slot_name, slot_type, active FROM pg_replication_slots;

⚠️ 「0 行が正」はサービス依存

  • eligibility-verification: 外部 logical replication 無し → BG 用 slot が消えて 0 行が正
  • Datastream / DMS / CDC を使うサービス(例: online-karte-service): BG 用 slot が消えていることに加え、サービス固有の外部 replication slot / publication を再作成・再開する必要がある(移行に伴い slot は引き継がれない)。0 行を期待せず、**「BG 用 slot は消えた」かつ「外部レプリ用 slot を別途復旧した」**の2点で確認する。
  • ⚠️ 再作成時にバックフィルが走り得る(本番は枠内に終わらないおそれ)。停止→再開の段取り・監視(GCP/Slack)・実測値は サービス固有手順 services/<service>.md(例: online-karte-service §5)を参照。事前判定は プレフライト A-2

E-1. 性能監視(実トラフィックで最低3日)

実トラフィックの本番で監視する。

Top SQL を A-3 ベースラインと突き合わせ(queryid はアップグレードで変わるため、クエリ本文・呼出回数・mean/total 時間で比較):

sql
SELECT LEFT(query,100) AS query_preview, calls,
       round(mean_exec_time::numeric,2) AS mean_ms, round(total_exec_time::numeric,2) AS total_ms, rows
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;

退行が疑われるクエリは、A-3 で保存した EXPLAIN と事後 EXPLAIN を比較する:

sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ... ;  -- A-3 と同一クエリ

比較ポイント: Seq Scan ↔ Index Scan / Nested Loop ↔ Hash Join ↔ Merge Join の変化 / actual time の増減 / rows(推定 vs 実行)の乖離。

メジャーバージョン間の主な Optimizer 変更(同じクエリでもプランが変わり得る):

バージョン主な変更影響
PG13インクリメンタルソート / B-tree 重複排除ソートを含むクエリのプラン変化
PG14Memoize 結合戦略Nested Loop 結合のプラン変化
PG15hash_mem_multiplier 既定 2.0ハッシュ結合のメモリ使用量増
PG16ウィンドウ関数最適化ウィンドウ関数クエリのプラン変化

任意の上級策: Aurora の apg_plan_mgmt(Aurora Query Plan Management) で、アップグレード前にプランベースラインをキャプチャし事後のプラン安定性を確保する方法もある(拡張有効化・運用オーバーヘッドを伴うため、退行リスクの高いクエリが多い場合に検討)。参考: Aurora PostgreSQL Query Plan Management

  • CloudWatch / Datadog: cpuutilization / read_latency / write_latency / commit_latency / database_connections / freeable_memory / free_storage_space / deadlocks を、PG13 時代の同時間帯と比較
  • 判定目安(#12725 §13): 主要 API エラー率が以前と同等以下 / 主要クエリのレスポンスタイムが120%以内 / CPU・メモリに異常増加なし / 最低3日間の安定稼働
  • PG16 後だけの統計にするなら、reset 前に Top SQL を保存 → SELECT pg_stat_statements_reset();SELECT * FROM pg_stat_statements_info;(reset 時刻記録)→ 一定期間蓄積して A-3 と比較。pg_stat_statements_reset() が消すのは SQL 実行統計のみ(データ・アプリに影響なし)。

E-2. Terraform 整合(★ドリフト防止・必須)

⚠️ Switchover 前に Terraform 整合 PR を作成しておき、Switchover 後すぐ plan 確認 → apply できる状態にしておく(GO/NO-GO チェック項目)。 ⚠️ Switchover 後、Terraform コードが PG13 のままで通常 terraform apply するのは禁止(PG13 へ戻す / replace される恐れ)。整合 apply まではコード変更系の apply を凍結する。

fastdoctor-template/eligibility-verification/production/ を 16 系へ更新する:

  • rds_engine_version"13.20"16 系(例 "16.13" へ。
  • parameter group の family を aurora-postgresql16 に、PG16 parameter group 参照へ修正(A-1 で作った PG16 用 CPG/Instance PG を本番クラスタの正とする整合)。
  • terraform plan で downgrade / replace(ForceNew)が出ないことを確認してから apply。更新しないと「16→13」ドリフトになる。

E-3. 後始末(旧 PG13 は数日安定後)

  • 新 PG16 を残す
  • 旧 PG13(eligibility-verification-old1)は数日の安定稼働を確認してから削除(ロールバックの緊急退避候補として一時保持)。

⚠️ 本番旧DB の削除は --final-db-snapshot-identifier で final snapshot を残すのを標準にする(--skip-final-snapshot をデフォルトにしない)。旧 Blue は Switchover 直前まで同期された状態を持つため、削除時にも最終スナップショットを残す方が安全。インスタンス名・クラスタ名は API から取得(決め打ちしない)。

bash
# 数日安定後に BG レコード削除(--delete-target は付けない=Switchover 後)
aws rds delete-blue-green-deployment --blue-green-deployment-identifier $BG --region $R --profile $P

# 旧 PG13 の実インスタンス/クラスタ名を取得(${SOURCE_CL}-old1 を決め打ちしない)
OLD_CL=${SOURCE_CL}-old1
OLD_INSTANCES=$(aws rds describe-db-clusters --db-cluster-identifier "$OLD_CL" \
  --query "DBClusters[0].DBClusterMembers[].DBInstanceIdentifier[]" --output text --region $R --profile $P)

# インスタンス削除(複数あれば全て)→ クラスタ削除(final snapshot を残す)
for OID in $OLD_INSTANCES; do
  aws rds delete-db-instance --db-instance-identifier "$OID" --skip-final-snapshot --region $R --profile $P
  # ※ instance 単位の final snapshot は Aurora では cluster snapshot 側で担保するため skip 可
done
FINAL_SNAP=${OLD_CL}-final-$(date -u +%Y%m%d%H%M)
aws rds delete-db-cluster --db-cluster-identifier "$OLD_CL" \
  --final-db-snapshot-identifier "$FINAL_SNAP" --region $R --profile $P

⚠️ 削除は安定確認が済んでから-old1 を消すと事前 snapshot(D-0)以外の切り戻し元が無くなるため、cluster 削除時に final snapshot(PG13)を必ず残す。順序・snapshot 指定は実 Aurora 構成に合わせて調整。

E-4. logical replication の恒久維持/戻し判断

rds.logical_replication / wal_sender_timeout を恒久維持するか戻すかは #13044 で判断する。


メンテ枠の時間設計(フェーズD・1時間枠前提)

考え方:Switchover 直前までの「時間がかかるが無停止」の作業はすべて枠前に終わらせ、1時間枠は「Switchover + 事後検証 + ロールバック判断バッファ」に集中する。 ダウンタイムが出るのは Switchover だけ(フェーズC の Green 作成・検証・ANALYZE は Blue 無停止)。

枠までに完了させること(枠外・無停止/別枠)

作業いつ備考
Terraform で本番 CPG(PG13 custom + PG16 target)追加・apply枠前(日中可)フェーズA
logical 有効化:custom CPG 付け替え+再起動(瞬断)別の事前メンテ枠(Switchover 枠とは分ける)フェーズB。⚠️ source が既に logical_replication=on(例: Datastream 運用サービス)なら不要
Blue/Green 作成(Green を AVAILABLE まで・約31分)/Green 検証/ANALYZE枠前(必須)フェーズC。31分を枠に入れないのが1時間死守の肝
ベースライン取得 / DDL・migration 凍結 / 周知 / DNS TTL≤5s / 監視 mute枠前フェーズA

⚠️ Green が枠前に AVAILABLE でないと1時間に収まらない。 フェーズB の再起動(瞬断)も Switchover 枠とは別に取る。

枠の「直前」にやること(T-15〜T0・無停止・1時間枠には含めない)

snapshot・最終確認はすべて無停止で、Switchover の数十分前に終えられる。1時間枠(=Switchover 以降)に含めず、枠を開く前に済ませる。ここで NG が出たら枠を開かずに延期できる(readiness ゲート)。

T-15〜-8 : 最終ゲート(BG AVAILABLE / StatusDetails=null)+ レプリラグ≒0 確認(D-1)
T-8〜-3  : PG13 スナップショット取得 → available(D-0・約2〜3分)
           ※ rollback の RPO 最小化のため Switchover の直前に取る(何日も前に取らない/枠の~15分前なら十分新鮮)
T-3〜0   : DDL/migration 凍結の最終確認・関係者/監視 mute の最終確認・GO/NO-GO 判断

⚠️ ここはダウンタイムなし。NG(ラグが大きい / snapshot 失敗 / StatusDetails≠null)なら枠を開かず延期する。

1時間メンテ枠の中身(T0=Switchover)

─────────── メンテ枠 開始 (T0) ───────────
T+0〜+2   : Switchover 実行(書込断 数秒)(D-2/D-3)
T+2〜+20  : PG16 疎通 / アプリ主要機能・再接続確認(D-4)/ ALTER EXTENSION UPDATE / slot 確認(D-5)
T+20〜+60 : 監視・ロールバック判断基準に抵触しないかバッファ(40分)
─────────── メンテ枠 終了 ───────────
(枠後・数日): 実トラフィック監視 / Terraform 16系整合 / 旧PG13(-old1)削除(フェーズE)

実測に基づく所要(1時間枠が現実的な根拠)

  • PG13 スナップショット → available: eligibility / online-karte の Clone リハーサルでは**数分(実測 online-karte 1.7GB=2分40秒)**で完了したが、DBサイズ・更新量・AWS 側状態により変動するため全サービス共通の固定値とはしない。枠直前(D-0)に着手し、available を確認してから Switchover に進む(枠の時間は消費しない)。
  • Switchover: in_progress→completed 約40秒〜1分・書込断 約2〜4秒(実測)。ただし lag=0 を待つため書込が多いと延びる--switchover-timeout 既定 300秒=最悪5分)→ 低トラフィック帯で実施しラグを抑える。
  • ALTER EXTENSION / slot 確認: 数秒。アプリ疎通: 数分。
  • → 枠内の実作業(Switchover〜検証)は 約20分で収まり、残り 40分をロールバック判断に充てられる。Switchover が最悪5分かかっても余裕で枠内

フェーズB(CPG 付け替え+再起動)は瞬断ありなので、Switchover 当日とは別の事前メンテ枠で実施する(source が既に logical on のサービスは不要)。


ロールバック

タイミング対応
Switchover 前(Green 検証中)BG を削除すれば Blue 本番に影響なし。aws rds delete-blue-green-deployment --blue-green-deployment-identifier $BG --delete-target ...--delete-target で Green も破棄=中止)
Switchover 中(タイムアウト超過)AWS 側で自動ロールバック(両環境に変更なし)。結果を確認
Switchover 後PG16→13 にその場で戻すことは不可。 第一選択は PG16 で fix-forward(データ欠損なし)。緊急退避は 旧 Blue(-old1/PG13)切り戻し(接続先切替・ECS 再起動・writable 確認が必要/Switchover 後の書込は旧 Blue に無い)。最終保険は 事前スナップショット復元(→ 下記)

ロールバック判断基準(#12725 §13・継続時間を満たしたら判断へ):

  • 主要 API エラー率 > 1% が 5分以上継続
  • レスポンスタイムが通常時の 3倍以上10分以上継続
  • DB 接続エラーが 5分以上 / 主要機能が利用不能が 3分以上 / 書き込み失敗が 3分以上継続

正本は #12725「13. ロールバック方針」スナップショットからの復元(接続先切替・ECS 再起動含む詳細手順)は Clone リハーサル版「付録: スナップショットからの復元(本番ロールバック詳細)」 を参照(本番の eligibility-verification 前提でそのまま使える)。

⚠️ Switchover 後は $SOURCE_CLPG16 新本番を指す。PG13 を復元したいのに $SOURCE_CL を source/対象にしないこと(旧 PG13 は -old1)。標準は PG13 手動スナップショット(D-0)からの復元


完了条件

  • [ ] 本番 eligibility-verification が PG16(16.x)として稼働し、アプリ主要機能(OCR read/write 等)が正常
  • [ ] 拡張が PG16 既定版に揃い、BG slot が解消されている
  • [ ] 実トラフィックで最低3日の安定稼働を確認(性能退行なし)
  • [ ] Terraform を 16 系へ整合(rds_engine_version・family)し、plan に downgrade/replace が出ない
  • [ ] 旧 PG13(-old1)を安定確認後に削除