Skip to content

mental-appointment PG16 staging テスト実施手順(★非-Global・2系統アプリ互換検証)

staging クラスタ(Aurora PG 13.23・非-Global・単一インスタンス)で PG13→16 を 標準(非-Global)Blue/Green で通すテスト手順。主眼は PG16 での 2系統アプリ互換(mental-appointment-service / online-ops-service / Retool / Trocco)と手順素振り。

  • 本番手順書(正本・固有値): preflight.md(§0 アーキ・§3 consumer・§6 CPG 設計・§9 検証)/clone(Global 削除経路): clone-rehearsal.md
  • 役割分担: 本書=2系統アプリ互換 E2E が主眼。Global 削除経路は clone-rehearsal.md が主担当(staging は非-Global)。本番当日手順は production.md(#17392・別途)。
  • 実施 issue: #17391(手順書)/ #17283(staging テスト実施)/親 #17280

1. 環境(staging・profile staging-admin

項目
アカウント / リージョン301608970378 / ap-northeast-1
clustermental-appointment(Aurora PG 13.23非-Global単一インスタンス mental-appointment-0db.t3.medium(ap-northeast-1c)・実測 2026-08-18)
cluster PGcustom mental-appointment(実測: rds.logical_replication=1(user)/shared_preload_libraries=pgaudit,pg_stat_statements(user)/wal_sender_timeout=0)=既に logical=on・pgaudit 済cluster_parameter_group_custom_enable=true
endpointwriter mental-appointment.cluster-ctb7v7xkimcr.ap-northeast-1 / reader …cluster-ro-ctb7v7xkimcr…(単一インスタンスなので ro も同一インスタンス)
DB SGsg-044fde676a4553769(5432 inbound。許可 SG に mental-appointment-service sg-01f3935c7fba1da5c・online-ops-service sg-0d46c19ecedbcb45c を含む)
13.23→16 target16.14(16.11/16.13/16.14 が有効・preflight.md §9-A
踏み台i-0ddd2353e102d9006(fd-platform・本 preflight/実測で使用)
consumermental-appointment-service(ECS mental-appointment-cluster・writer)/ online-ops-service(ECS online-ops-service-cluster・reader)/ Retool / Trocco / 踏み台
v2 実ストア(対象外)online_ops_service(online-ops 自前 DB・ap-ne-1・既に PG16=16.11)
監視ダッシュボードCloudWatch mental-appointment-pg16-bluegreen(ap-northeast-1・staging/production 両方作成済み・PR #2644/#17414)。DatabaseConnections / AuroraReplicaLag / OldestReplicationSlotLag / DBLoad / TransactionLogsDiskUsage / CPUUtilization / FreeableMemoryblue+green 同一画面で追える(green は SEARCH で自動収集)。F-Gate の lag 確認・§G-2 の復旧確認で使う

2. 本番との差(=手順の読み替え)

差異本番(us-east-1)staging(ap-ne-1)影響
Global DBあり(単一リージョン)なし実移行は最初から標準 BG。Global 削除経路は clone-rehearsal.md で担保(staging は §7 で素振り可)
インスタンスWriter -0+Reader -1(2台)単一インスタンス -0付替後 reboot は1台のみ(順序考慮不要)
source cluster PGdefault(logical off)→付替+reboot 必須custom mental-appointment(logical=1・pgaudit 実測済)staging は source 付替が実質不要(既に logical=on。実行直前に SHOW で runtime も確認)。作業は target PG16 CPG を作るだけ
クラスdb.r7g.large ×2db.t3.medium ×1性能ベースラインは参考値。★t3 系は Aurora Global 非対応=§7 の Global 素振りは class 一時変更が必須
バージョン13.2313.23target 16.14 で共通

3. パラメータグループ(TF)

preflight.md §6 と同じ共通モジュール aurora-bluegreen-param-groups で staging にも作成済み(fastdoctor-template/pf-mental-appointment-service/staging/pg16_param_groups.tf・PR #2633):

  • target(PG16)=mental-appointment-pg16(cluster+instance)
  • source(PG13 logical=1) は既存の custom CPG mental-appointment をそのまま使用(付替不要)。実測 2026-08-18: このCPGは rds.logical_replication=1shared_preload_libraries=pgaudit,pg_stat_statementswal_sender_timeout=0(いずれ Source=user)=BG 前提を満たしている。→ 本番用 mental-appointment-pg13-bg(#17389)の付替は staging では不要。

⚠️ pgaudit:staging source CPG は既に pgaudit,pg_stat_statements を preload 済(実測)。target PG16 CPG も pgaudit を含む(#2633 で対応済)。→ source/target とも pgaudit 一致(BG で監査ログが欠落しない)。

4. テスト実施(標準 BG)

⚠️ 本節は「standalone(非-Global)クラスタに対する標準 BG」から始まる。staging に Global DB は無いため、本番の「Global あり → 削除 → 標準 BG」という前半(Global 作成・削除)は本節に含まれない

  • Global 作成→削除の CLI 手順は本書 §7 にある。本番順序まで再現したい場合は §7 ①②③ を本節の前に実施する(§7 の「実施順序」注記を参照)。
  • Global 削除 → 標準 BG の接続部分clone-rehearsal.md((2) Global 化 →(3) Global 削除 →(5) 標準 BG)が主担当。
bash
export AWS_PROFILE=staging-admin ; R=ap-northeast-1
CL=mental-appointment

# F-1. 事前確認(staging は source が既に logical=on=付替不要。確認のみ)
#   ※ CPG `mental-appointment` は logical_replication=1/pgaudit,pg_stat_statements/wal_sender_timeout=0(実測2026-08-18・付替不要が確定)
#   踏み台(i-0ddd2353e102d9006) psql で runtime も確認: SHOW rds.logical_replication;=on / SHOW wal_sender_timeout;=0
#   もし runtime が off なら custom CPG を reboot で反映(本番と同様。staging は単一インスタンスなので -0 のみ reboot)
#   ⚠️ この reboot 分岐を踏む場合のみ **書込断(瞬断)が発生** → 実施前に staging 利用者へ周知する(dr-work-record staging F-2 と同様)。
#      単一インスタンスなので Writer だけの再起動で逃がすことができない=断は avoid 不可。
#      付替不要(実測どおり runtime が on)なら断は Switchover の数秒のみ=周知不要。

# F-1.5. ★BG 作成前に blue で pg_stat_statements 拡張を作成(Green は同期中 read-only で後付け不可)
#   踏み台 psql: CREATE EXTENSION IF NOT EXISTS pg_stat_statements;  → \dx で installed 確認
#   BG 作成前チェック: available / global=null(非-Global) / CPG in-sync / 長時間tx=0 / read_only off

# F-2. 標準 BG 作成(非-Global・target 16.14・PG16 target PG)
CL_ARN=$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].DBClusterArn' --output text)
aws rds create-blue-green-deployment --region $R \
  --blue-green-deployment-name mental-appointment-bgtest \
  --source "$CL_ARN" \
  --target-engine-version 16.14 \
  --target-db-cluster-parameter-group-name mental-appointment-pg16 \
  --target-db-parameter-group-name mental-appointment-pg16

# F-3. Green 検証(generic 手順 6)→ ★Switchover 前ゲート(下記)→ Switchover(手順 7・書込断 数秒)→ 事後(手順 8)
  • [ ] SHOW rds.logical_replication;=on(付替不要の確認 or 付替後)
  • [ ] blue で CREATE EXTENSION pg_stat_statements(後付け不可のため BG 前)
  • [ ] Green(PG16) AVAILABLE・論理レプリ同期(generic 手順 6)
  • [ ] Switchover:rename・endpoint 据え置き・書込停止 約60〜68秒(★staging 実測 2026-08-19。本番相当 clone 実測は約70秒)

★所要時間(staging 実測 2026-08-19 / 本番 us-east-1 clone 実測 2026-08-17・clone-rehearsal.md

作業staging 実測本番 clone 実測
BG 作成(AVAILABLE まで)35分未満約40分Blue 無停止
Switchover(書込停止)約60〜68秒約70秒★断
green で ANALYZE VERBOSE(全16表)2秒27.2秒なし
保険スナップショット(available まで)4分02秒なし
(§7 実施時)instance class 変更 1回7〜8分★断

作業枠はこの実測を基準に見積る。staging は本番よりデータ量が小さいため各所で短いが、Switchover の書込停止はほぼ同等(60〜70秒)。

⚠️ StatusDetails の型が完了時に変わるSWITCHOVER_IN_PROGRESS 中は配列(または null)だが、完了時に 文字列 "Switchover completed" になる。JMESPath で join(\,`, StatusDetails)を使うポーリングは完了時にIn function join(), invalid type for valueで壊れる(実測)。ポーリングはStatus だけを見るか、StatusDetails` を素で出す。

F-Gate. ★Switchover 前ゲート(GO/NO-GO)

BG describe だけで済ませず green に実接続して検証する(clone-rehearsal / 本番 production.md と統一):

  • [ ] BG Status=AVAILABLE / StatusDetails=null(Replication degraded でない)
  • [ ] SwitchoverDetails の各リソースが AVAILABLE(cluster・instance)
  • [ ] green member CPG が in-syncDBClusterParameterGroupStatus。pending-reboot なら green を reboot)
  • [ ] replica lag ≈ 0OldestReplicationSlotLagAuroraReplicaLag
  • [ ] 長時間トランザクション = 0(blue で pg_stat_activitystate<>'idle' AND xact_start IS NOT NULL
  • [ ] ★Green 接続検証(green cluster endpoint へ踏み台 psql): SHOW server_version=16.14主要テーブル件数が blue と一致appointments/patients/appointment_patient_histories)/ default_transaction_read_only=on(同期中は正常)/ SHOW shared_preload_librariespgaudit 含む
  • [ ] ★必須:green で SET default_transaction_read_only=off; から 全テーブル ANALYZE VERBOSE;(Switchover 前・切替直後の性能事故防止。任意ではなく必ず実施)
  • [ ] 手動スナップショット(ロールバック保険): aws rds create-db-cluster-snapshot --db-cluster-identifier mental-appointment --db-cluster-snapshot-identifier mental-appointment-pre-pg16-<YYYYMMDDHHMM>wait db-cluster-snapshot-available(engine=13.23 確認)
  • [ ] ★Terraform 整合 PR を準備(Switchover で実体が 16.14 になるが TF は 13.23 のまま→次 apply で downgrade 宣言=replace 危険)。§6-④ の「継続構成B」の内容で PR を作り、切替後すぐ apply できる状態にする(実績: terraform_for_aws#2665)
    • ⚠️ terraform.tfvars を変更する PR は作れない*.tfvars.gitignore 対象(S3 管理)。実機確認でも tfvars は rds_engine_version設定しておらず、実効値は variable.tfdefault
    • ⚠️ engine version だけでは plan が no-diff にならない:Switchover 後は CPG の実体が mental-appointment-pg16(family 16)になるため、**family と CPG 参照もあわせて 16 系へ寄せる(継続構成B)**必要がある。詳細と import 手順は §6-④。

5. ★§G. 2系統アプリ互換検証=アプリ側 E2E(本 staging テストの主眼・#17283)

★大前提:2系統(新規予約では mental_appointment を検証できない)

2026-06-01 以降の新規予約(時間帯予約)は v2(online_ops_service) に書かれ、移行対象 mental_appointment を通らないpreflight.md §0)。

  • 「新規予約を作成 → 確認」は空振り(v2 へ行く)。
  • 正しい E2E は「固定時間予約(6/1以前・legacy)」を read/edit する(後述の FIX 案件)。

consumer アプリと接続経路

consumermental_appointment への接続driver
mental-appointment-service(ECS mental-appointment-clusterwriter(患者情報編集の legacy 書込)Prisma 5.13.0
online-ops-service(ECS online-ops-service-clusterreader(診察履歴の固定時間予約表示)Kysely 0.27.3 + pg 8.11.5(kyselyAppointment
Retool / Troccoreader(運用/ETL)JDBC

4層で確認(staging・staging-admin

確認方法
①インフラECS mental-appointment-service / online-ops-service-service RunningCount=Desired / ALB target healthy
②API/ヘルスmental-appointment-service は API 直プローブ(DB read エンドポイント・下記手順)で接続確認。⚠️ /health は liveness のみ(DB を叩かない)。online-ops は deep-health(appointment-db インジケータ・#17413)があれば併用
③APM/ログDatadog service:mental-appointment-service / service:online-ops-service の Errors で Prisma/pg 接続エラー無し
④ユーザー E2Estaging は OPS UI 実機が使える方針=“接続確認”(両 ECS が Aurora に繋がることを read で確認。write は任意)

★テスト方針(確定)=接続確認:本 staging テストの目的は 「各 app ECS ↔ Aurora の接続が Switchover 後も生きているか」。read/write のどちらでも「その ECS が Aurora に到達・クエリできる」ことは確認できるため、両 ECS とも read で接続確認する(online-ops・mental-appointment-service とも)。write(書込可否=昇格成功の検証)は任意(必要な場合のみ実施。切替後に“書ける”かまで見たいときの追加項目)。

★E2E FIX 案件(接続確認)— Switchover 前後で同一手順

ブックマークhttps://online-karte.fstdr.app/ops/mental/09d77b1f-f200-496e-bd88-21117ac6f9db(患者「よねまる いち」・memberId 306877・固定時間予約 2026-05-31)

確認対象(ECS)操作期待(成功時)補足
online-ops-service → Aurora(reader) 接続上記 URL を開く予約情報に**「予約種別=固定時間予約」**が表示(診察履歴の固定時間予約行も可)kyselyAppointment.replica(true).selectFrom('appointments')。表示=read 経路生存(消えたら断)
mental-appointment-service → Aurora(writer ep) 接続API 直プローブ(主手段):admin read API GET /v1/admin/appointments/{09d77b1f} を踏み台→内部ALB で叩く(下記「API 直プローブ手順」)200+予約 JSON が返るDATABASE_URL=cluster(=writer) ep なので、この read で writer ep 接続まで確認(write 不要)。⚠️ /health は liveness のみ(DB を叩かない)ため接続確認に使えない
(任意)writability(切替後に書けるか)同予約の患者情報(氏名)を編集appointment_patient_histories 追記(実証: いち→に で 1→2 行・10.10.3.65COMMIT 秒一致)接続確認が目的なら不要。昇格成功・データ永続まで見たい場合のみ
(任意)doctor_appointment_blocksUI 不可(v2 へ書く)。書込健全性を見るなら継続書込監視

★API 直プローブ手順(mental-appointment-service → Aurora 接続確認・主手段)

前提

  • #17394(staging)/#17398(本番)で踏み台→内部ALB(80) の SG 許可が適用済み(dr-work-record #2574/#2610 相当。未適用だと踏み台から叩けない)。
  • **admin CAT(ServiceAccount トークン)**を用意(admin/subgraph API は要認証・preflight.md §9)。
  • ⚠️ /health は liveness のみ(実装 health.check([])=DB インジケータ無し)→ Aurora 接続確認には使えないDB を read する admin エンドポイントを叩くこと。
bash
export AWS_PROFILE=staging-admin
# staging の内部ALB DNS を確認(ap-northeast-1)
ALB=$(aws elbv2 describe-load-balancers --region ap-northeast-1 \
  --query "LoadBalancers[?contains(LoadBalancerName,'mental-appointment')].DNSName" --output text | head -1)
# 既知の legacy 予約(09d77b1f)を admin API で読む=mental-appointment-service→mental_appointment read
curl -s "http://$ALB/v1/admin/appointments/09d77b1f-f200-496e-bd88-21117ac6f9db" \
  -H "x-access-token: <ServiceAccount CAT>" -w ' [%{http_code}]'
  • 期待200 + 予約 JSON が返る=mental-appointment-service が Aurora(writer ep) を read できている。Switchover 前後で同 curl を実行し 200+データが返り続けることを確認。
  • ★本番実施時(production.md #17392 でも同様)09d77b1f は staging の予約なので、本番では 本番 mental_appointment の予約 id を read で1件選んで --appointment-id/URL に使う(テストデータ投入は不要・既存を読むだけ)。mental_appointmentappointments は全て固定時間予約(legacy)なので任意の1件でよい:
    bash
    # 踏み台 read-only psql で本番の予約 id を1件取得(当日どの id を使うか迷わないよう事前に選定)
    psql ... -tAc "SET default_transaction_read_only=on; SELECT id FROM appointments ORDER BY created_at DESC LIMIT 1;"
    ※ 検証スクリプト pg16-verify-mental-appointment.sh --api-probe --appointment-id <選んだid> で渡す。存在しない id でも find() クエリは走り 404 が返る=DB 到達は証明できるが、200 になる実在 id が綺麗。
  • 複合確認(CAT を用意できない場合の堅牢な代替):踏み台 read-only psql で pg_stat_activity10.10.3.65(mental-appointment-service) の established 接続+直近 SELECT があること(eager $connect+背景処理で常時接続)を確認=API 認証なしで ECS↔Aurora 接続を担保できる。

★DB 直での接続元→ECS 確認(read/write を ECS まで特定)

pg_stat_activity.client_addr を ECS タスク IP に逆引きして「どの ECS が接続・処理したか」を確定(preflight.md §0 の手法):

  • IP→ECS:10.10.3.65=mental-appointment-service(writer)/10.10.25.165=online-ops-service-service(reader)
  • ⚠️ online-ops の read 接続は idle 約10秒で回収されるため、UI 操作中に 1〜2秒間隔で連続サンプリングしないと SELECT appointments を捕捉できない。
  • 踏み台 read-only(SSM port-forward)で SET default_transaction_read_only=on; の上、pg_stat_activity(client_addr)+pg_stat_database(tup デルタ)を参照。

チェックリスト(§G)

  • [ ] ①②③:BG 作成・Switchover の前後で ECS healthy・Datadog(mental-appointment-service / online-ops-service)エラー無し
  • [ ] online-ops 接続確認(read)09d77b1f を開き「固定時間予約」表示(Switchover 前後)+ pg_stat_activity で online-ops(10.10.25.165) の SELECT appointments を連続 poll で捕捉
  • [ ] mental-appointment-service 接続確認(read・主手段=API 直プローブ)GET /v1/admin/appointments/{09d77b1f}(#17394/#17398 SG+admin CAT 前提)が 200+予約 JSON。※/health は liveness のみで不可。補助として pg_stat_activity で 10.10.3.65 の established+SELECT を確認(writer ep 接続)
  • [ ] (任意)writability(write):接続確認が目的なら不要。切替後に書けるかまで見る場合のみ 09d77b1f の患者情報編集で appointment_patient_histories 追記+10.10.3.65 の COMMIT 一致
  • [ ] (任意)doctor_appointment_blocks:書込健全性を見る場合のみ max(updated_at)/n_tup_ins の継続書込監視
  • [ ] pgaudit:PG16 green で SHOW shared_preload_libraries; に pgaudit 含む・監査ログが Switchover 後も出続ける
  • [ ] Retool / Trocco:Switchover 後に再接続・疎通(間欠のため次回接続で確認)
  • [ ] 接続断→再接続:Switchover 時に mental-appointment-service / online-ops がクラッシュ/スタックしない(cluster endpoint なので自動再接続想定・残留時 §G-2)

§G-1. アプリ E2E 実施メモ

  • staging は OPS UI(online-karte.fstdr.app/ops/mental)が使える(mental-appointment を次に選んだ理由=E2E 容易・preflight.md)。ログインは事務局アカウント。
  • read/write とも上記 FIX 案件 09d77b1f(よねまる いち) 1件で完結(DB 裏取り済)。
  • 患者 WEB(online-appointment-app)の新規予約は v2 へ行くため mental_appointment 検証には使わない(2系統)。
  • online-ops の read 健全性は 本番でこそ主役(本番は Reader で大量 read・preflight.md §0)。staging でも上記 poll で捕捉可能。

§G-★. staging 実測結果(2026-08-18・移行前 pre 状態/#17283)

移行前 baseline として、SG 反映・deep-health 反映・両 ECS↔Aurora 接続を staging 実機で確認済み。

前提反映(マージ≠反映のため要手当て)

  • SG(#2643)=手動 apply 済み:ルール sgr-071b8af6c73c4de48(bastion fd-platform(sg-02d8de65b0be211d0) → mental-appointment-service ALB(sg-01c23c0b2077b29fc) :80)。
    • ⚠️ sg-associations/ は CI の staging@apply 対象外の独立 root(別 state mental-appointment/sg-associations.tfstate)。マージだけでは適用されず、terraform apply手動実行が必須(本番 #17398 も同様)。
  • deep-health(#17624)=反映済みonline-ops-service-service(cluster online-ops-service-cluster)の image = online-ops-service:sha-dbd7191。CodeDeploy d-PH29UNHTK が 2026-08-18 15:01(JST) Succeeded。

接続確認(pg16-verify-mental-appointment.sh --env staging

  • server_version = 13.23(移行前 PG13)。read プローブ成功:appointments=8168 / patients=1742 / appointment_patient_histories=8355。全16ユーザーテーブルの exact count 取得(/tmp/pg16-verify-ma-rowcounts-staging-*.txt)。
  • 接続元→ECS 属性化(pg_stat_activity・両 consumer ↔ Aurora 生存を実証)
    • mental-appointment-service(今回 10.10.19.78・writer 常時接続)→ established 3本(毎回捕捉)。
    • online-ops-service-service(今回 10.10.14.15・新 deep-health 版 sha-dbd7191)→ established 2本を捕捉。 ⚠️ online-ops の read は idle 約10秒で回収されるため間欠。1回目 run で 2本捕捉、直後の再 run では 0(想定どおり)。=deep-health appointment-dbkyselyAppointment.ping())が監視する接続そのもの。
    • ※ ECS タスク IP は再デプロイで変わるため、スクリプトが実行時に aws ecs から動的解決(IP 直書きしない)。
  • writability インジケータpg_is_in_recovery()=f(primary)。※読取専用トランザクション下のため default_transaction_read_only はスクリプトが on 設定。
  • deep-health appointment-db:ok の実挙動 → Datadog ログで確認(#17624):
    • GET /health/dependencies の HTTP 直叩きは 踏み台→online-ops ALB が SG 未許可・ECS Exec が TargetNotConnected のため未実施。代わりに Datadog ログで実挙動を確認できる(推奨・追加の SG 不要)。
    • 確認方法:Datadog Logs で service:online-ops-service "deep health overall"(cluster online-ops-service-cluster)を検索。
    • 読み方(コード仕様):deep-health は overall≠ok の時だけ warn ログを出し、down のチェックのみ列挙する(deep-health.service.ts: checks.filter(c => c.status !== 'ok'))。よって ログの down リストに appointment-db が無ければ ok。稼働コード(sha-dbd7191=#17624 込み)には appointment-db(kyselyAppointment.ping()) チェックが存在する。
    • 実測(2026-08-18 06:09Z・デプロイ後):deep health overall=degraded: [{"name":"eligibility-verification-service","status":"down","criticality":"informational","detail":"http-502"}]down は eligibility-verification-service(informational・外部HTTP)のみ・appointment-db は列挙なし=ok
    • ※ ログ version タグは release PR 未マージのため据え置き(1.108.0 表示でもコードは #17624 込み dbd7191)。literal 200 がどうしても要る場合のみ 踏み台→online-ops ALB(:80) の一時 SG 追加→curl(トークンは踏み台上で取得・非露出)→削除。

§G-2. 接続残留時の復旧手順(Switchover 共通)

cluster endpoint 接続(DNS は切替で不変)→基本は自動再接続で自己回復。残留時のみ対応:

  1. 検知:Switchover 直後は一時的に接続エラーが出るが数分で消えれば正常。残るかを見る。
    • Datadog service:mental-appointment-service / service:online-ops-service の Error(Connection terminated/server closed the connection/Can't reach database server
    • CloudWatch ダッシュボード mental-appointment-pg16-bluegreen(ap-northeast-1・staging/production 両方に作成済み・PR #2644/#17414)で DatabaseConnections の回復を確認。green も SEARCH で自動収集されるため、同期中 → Switchover 前後 → 事後まで同じ画面で追える(AuroraReplicaLag / OldestReplicationSlotLag は F-Gate の lag 確認にも使う)。
  2. mental-appointment-service(Prisma・writer)復旧:残留時 ECS ローリング再起動で新規接続を強制:
    bash
    aws ecs update-service --region ap-northeast-1 \
      --cluster mental-appointment-cluster --service mental-appointment-service --force-new-deployment
    • 復旧確認=API 直プローブGET /v1/admin/appointments/{09d77b1f}200+予約 JSON(上記「API 直プローブ手順」/pg16-verify-mental-appointment.sh --api-probe)。
    • ⚠️ /health は liveness のみ(DB を叩かない)ので復旧判定に使えない。dr-work-record staging の /healthCheck=200 に相当する確認は、この DB を read する admin エンドポイントで行う。
  3. online-ops-service(Kysely・reader)復旧:同様に online-ops-service-cluster / online-ops-service-service--force-new-deploymentkyselyAppointment の pool を張り直す。
    • 復旧確認(主手段)=OPS UI の固定時間予約行online-karte.fstdr.app/ops/mental/{id} を開き、診察履歴に「固定時間予約」行(6/1以前)が表示されること。表示=kyselyAppointment.replica(true).selectFrom('appointments') 経路の生存。
    • 復旧確認(補助)=deep-health(#17624):Datadog Logs service:online-ops-service "deep health overall" の down リストに appointment-db が無いこと。 ⚠️ 2026-08-18 17:22 以降、この deep-health は appointment-db: down (timeout) を出し続けている既存事象がある(DB 側は健全=踏み台から reader ep へ 0.12 秒で応答。ping()SELECT 1 は DB に到達・完了しているのにアプリ側が 2 秒で timeout 判定。pool の idle 回収による毎回コールドスタートが疑い)。この状態では deep-health を判定に使えないため、上の UI 確認を主手段にする。
    • 補助:pg_stat_activity で online-ops の ENI IP からの established を確認(⚠️ read 接続は idle 約10秒で回収されるため間欠。UI 操作中に連続 poll)。
  4. Retool:次のクエリ実行で自動再接続。残留なら Test connection/コンテナ再起動。
  5. 確認(総合):OPS UI の read 接続確認(+任意で write)を再実行、上記 2/3 の復旧確認が通る、Datadog Errors 解消、ダッシュボードで DatabaseConnections 平常化。

§G-3. ECS が Aurora に接続できない場合のトリアージ → 復帰

§G-2 の --force-new-deployment で直らない/再デプロイ後も繋がらないときはここ。**「残留(古い接続を掴んでいる)」ではなく「到達できない」**を切り分ける。

0. 症状の見分け(★ECS/ALB のヘルスでは判定できない)

観測意味
mental-appointment-service: admin read API が 5xx / タイムアウトwriter(cluster) ep に到達できない
OPS UI の患者診察履歴から 固定時間予約(6/1以前)の行が消えるonline-ops が mental_appointment に繋がっていない。read-repository.ts L220 の if (!isConnected()) return [] ガードで例外を出さず静かに空になるpreflight.md §3)=エラーログが出ないので見落としやすい
Datadog Appointment database pools initialized successfully は出ている⚠️ 到達性の証跡にはならない(env が揃って pool を作れただけ・下記 2)
ECS RunningCount=Desired・ALB target healthy⚠️ online-ops では未接続でもこうなる(下記 2)。復帰判定に使わない
deep-health の down リストに appointment-db実接続 NG(online-ops で唯一の実接続確認)

1. 到達性の切り分け(上から順に。①②で DB 単体の生死を確定させてから SG/endpoint を疑う)

bash
export AWS_PROFILE=staging-admin; R=ap-northeast-1; CL=mental-appointment

# ① クラスタ状態と endpoint(Switchover 後は writer/reader が green 実体を指す。DNS 名自体は不変)
aws rds describe-db-clusters --region $R --db-cluster-identifier $CL \
  --query 'DBClusters[0].{engine:EngineVersion,status:Status,writer:Endpoint,reader:ReaderEndpoint,cpg:DBClusterParameterGroup}'

# ② 踏み台 psql で DB へ直接(アプリを介さず DB 単体の生死・書込可否を確定)
#    server_version / read プローブ / client_addr→ECS 逆引き / pg_is_in_recovery・default_transaction_read_only
#    までスクリプトが一括で見る(踏み台 i-0ddd2353e102d9006・SSM ポートフォワード・read-only)
docs/sre/scripts/pg16-verify-mental-appointment.sh --env staging
#    ⚠️ Secret `mental-appointment` は値に制御文字を含む=JSON パースは strict=False 必須(preflight.md §付録)

# ③ DB SG inbound(5432) に両 ECS の SG が残っているか
aws ec2 describe-security-groups --region $R --group-ids sg-044fde676a4553769 \
  --query 'SecurityGroups[0].IpPermissions[?FromPort==`5432`].UserIdGroupPairs[].GroupId' --output text
#    期待: mental-appointment-service sg-01f3935c7fba1da5c / online-ops-service sg-0d46c19ecedbcb45c を含む

# ④ ECS が見ている接続先(secret の host が blue/green どちらを指すか)
#    mental-appointment-service … secret `mental-appointment` の DATABASE_URL(=cluster/writer ep)
#    online-ops-service        … secret `online-ops-service` の APPOINTMENT_DATABASE_URL(writer) /
#                                 APPOINTMENT_REPLICA_DATABASE_URL(cluster-ro)
#    ★online-ops の実接続は **reader のみ**(本番実測・preflight.md §3)→ reader ep の到達性を先に見る
  • ②で落ちる → DB 側(BG 同期中の read-only/green 未昇格/クラスタ status)。②の writability インジケータ(pg_is_in_recovery()=false / default_transaction_read_only=off)を確認する。Switchover 前の同期中は green が read-only なのが正常(F-Gate 参照)。
  • ②が通るのに ECS だけ繋がらない → SG / endpoint / secret 側。③④の順に潰す。

2. ★online-ops の graceful degradation の罠(ここで判断を誤りやすい)

  • src/database/kysely-appointment.service.tsisConnected()env(APPOINTMENT_DATABASE_URL / APPOINTMENT_REPLICA_DATABASE_URL)の有無だけで立てたフラグを返す。onModuleInit の接続ヘルスチェックはコメントアウトされたまま// Currently commented out to avoid blocking startup)。
  • Aurora に到達できなくても ECS タスクは起動成功し、ALB target も healthy になるAppointment database pools initialized successfully のログも「pool を作れた」だけで到達性を意味しない。
  • 復帰判定の主手段は「OPS UI に固定時間予約(6/1以前)の行が表示されること」online-karte.fstdr.app/ops/mental/{id} の診察履歴)。ECS/ALB のヘルスは使えないので、実データが画面に出ることを唯一の合格条件にする。
  • → deep-health appointment-dbkyselyAppointment.ping()・#17624)は補助。⚠️ 2026-08-18 17:22 以降 down (timeout) を出し続ける既存事象があり、これ単独では判定できない(DB 側は健全・SELECT 1 は完了しているのにアプリが 2 秒で timeout。§G-2 の注記参照)。deep-health が down でも UI に固定時間予約行が出ていれば read 経路は生存と判断してよい(2026-08-19 の Switchover 前後で実証済み)。
  • → UI 側の証跡は 固定時間予約(6/1以前)の行が表示されること。空のままなら未接続を疑う(例外にならないので Errors だけ見ていると気づけない)。
  • ※ mental-appointment-service(Prisma)はこの罠が無い代わりに /health が liveness のみなので、こちらも API 直プローブで判定する(§G-2 の 2)。

3. 直らない場合の打ち切り判断

  • Switchover 前:BG を破棄すれば blue(13.23) が無傷で継続=安全に撤退できる。
    bash
    aws rds delete-blue-green-deployment --region $R --blue-green-deployment-identifier $BG   # ★--delete-target は付けない
    (dr-work-record production.md §11 A と同じ。green リソースは残るので §7/後始末で別途削除してコストを止める)
  • Switchover 後(staging)fix-forward が方針procedure-staging-direct.md「staging なので緩め。失敗時は snapshot 復元 or 作り直しで可」)。F-Gate で取得した mental-appointment-pre-pg16-<YYYYMMDDHHMM> から作り直す/BG を張り直す。
  • 本番の切り戻しは本書の対象外production.md(#17392)に委譲。⚠️ Switchover 後の切り戻しは切替後に新 primary(16.14) へ入った書込が失われ、BG は一方向で逆 Switchover 不可(consumer を手動で戻す)。判断者・連絡経路・書込停止 or 差分許容の合意を先に取る(dr-work-record production.md §11 準拠)。

6. 事後(★Switchover 完了後・実施順)

2026-08-19 の staging 実施で実際に踏んだ順序とコマンド。①〜③は当日中に、④は Switchover 完了後すみやかに、⑤⑥は保険期間の判断後。

共通の前置き(psql は踏み台 SSM ポートフォワード経由。⚠️ Secret mental-appointment は値に制御文字を含むので JSON パースは strict=False 必須):

bash
export AWS_PROFILE=staging-admin; R=ap-northeast-1
CL=mental-appointment; BG=<bgd-xxxx>; BASTION=i-0ddd2353e102d9006
NEW=mental-appointment.cluster-ctb7v7xkimcr.ap-northeast-1.rds.amazonaws.com   # endpoint 据え置き=PG16 を指す

① 昇格確認(endpoint 据え置きで PG16 に繋がるか)

bash
aws rds describe-blue-green-deployments --region $R --blue-green-deployment-identifier $BG \
  --query 'BlueGreenDeployments[0].{status:Status,details:StatusDetails}'        # SWITCHOVER_COMPLETED
aws rds describe-db-clusters --region $R \
  --query 'DBClusters[?starts_with(DBClusterIdentifier,`mental-appointment`)].{id:DBClusterIdentifier,ver:EngineVersion,status:Status,cpg:DBClusterParameterGroup}'
sql
-- ★read-only ガードを付けずに実行する(付けると ro の本来値が見えない)
SELECT current_setting('server_version')               AS server_version,   -- 16.14
       pg_is_in_recovery()                             AS in_recovery,      -- f
       current_setting('default_transaction_read_only') AS ro,              -- ★off(同期中の green は on)
       inet_server_addr()                              AS server_ip,        -- 旧 green の実体
       current_setting('rds.logical_replication')      AS logical_rep,      -- on
       current_setting('wal_level')                    AS wal_level;        -- logical
SHOW shared_preload_libraries;                                             -- pgaudit を含む
-- Switchover 前に green で取った ANALYZE 統計が引き継がれているか
SELECT count(*) AS tables, count(last_analyze) AS analyzed, max(last_analyze) FROM pg_stat_user_tables;
  • [ ] SWITCHOVER_COMPLETED / 新 mental-appointment=16.14・CPG mental-appointment-pg16 / 旧が mental-appointment-old1=13.23
  • [ ] ★default_transaction_read_only=off(昇格成功の決定的証拠)・pg_is_in_recovery()=f
  • [ ] pgaudit 継続 / ANALYZE 統計 16/16 引き継ぎ(実測: last_analyze が Switchover 前の時刻のまま)

ALTER EXTENSION pg_stat_statements UPDATE

sql
-- 実行前: installed と default の差分を確認(実測 1.8 → default 1.10)
SELECT e.extname, e.extversion AS installed, d.default_version
  FROM pg_extension e JOIN pg_available_extensions d ON d.name=e.extname WHERE e.extname='pg_stat_statements';

ALTER EXTENSION pg_stat_statements UPDATE;

-- 実行後: installed が default と一致し、PG16 の列名で読めること
SELECT e.extname, e.extversion AS installed, d.default_version
  FROM pg_extension e JOIN pg_available_extensions d ON d.name=e.extname ORDER BY e.extname;
SELECT count(*) AS tracked, round(max(total_exec_time)::numeric,1) AS max_total_exec_ms FROM pg_stat_statements;
  • [ ] installed が default と一致(実測 1.8 → 1.10
  • [ ] total_exec_time で読める(PG12 起点なら列名は total_timetotal_exec_time に変わる。§C-1 の危険語スキャン対象)

③ 接続確認 E2E(§G と同じ手順を Switchover 後にもう一度)

bash
# 全16テーブル件数の pre↔post 自動 diff + 接続元→ECS 属性化 + writability
docs/sre/scripts/pg16-verify-mental-appointment.sh --env staging --label post
sql
-- 書いた ECS の特定(write E2E の裏取り)
SELECT client_addr, pid, state, date_trunc('second', state_change) AS last_activity,
       left(regexp_replace(query,'\s+',' ','g'), 70) AS last_query
  FROM pg_stat_activity WHERE client_addr IS NOT NULL AND backend_type='client backend'
 ORDER BY state_change DESC;
  • [ ] 全16テーブル件数が pre と一致(スクリプトが --label post で自動 diff)
  • [ ] mental-appointment-service(writer):OPS UI で対象予約の患者名を編集 → appointment_patient_histories新規行が追記され、その recorded_atpg_stat_activityCOMMIT の秒が一致(実測 2026-08-19 13:39:31)
  • [ ] online-ops-service(reader):OPS UI の診察履歴に固定時間予約(6/1以前)の行が表示(★主手段。deep-health は既存事象で使えない・§G-2 注記)
  • [ ] Datadog service:mental-appointment-service / service:online-ops-service に接続エラーの継続なし

④ Terraform 整合(★継続構成B・plan no-diff)

⚠️ terraform.tfvars ではない*.tfvars.gitignore 対象(S3 管理)で、実機確認でも tfvars は rds_engine_version を設定しておらず実効値は variable.tfdefault。 ⚠️ engine version だけでは no-diff にならない。Switchover 後は CPG 実体が mental-appointment-pg16(family 16)になるため、**live の -pg16 を継続利用する形(継続構成B)**に寄せる。実績 PR: terraform_for_aws#2665。

コード変更(別 PR・pf-mental-appointment-service/staging/):

  • variable.tf: variable "rds_engine_version" { default = "13.23" }"16.14"
  • main.tfmodule "microservice-ecs-tokyo":
    • rds_family = "aurora-postgresql16"(追加)
    • cluster_parameter_group_custom_enable = false
    • cluster_parameter_group_name = module.pg16_blue_green_param_groups.cluster_parameter_group_name
    • instance_parameter_group_name = module.pg16_blue_green_param_groups.db_parameter_group_name(追加)
    • create_instance_parameter_group = false(追加)
    • cluster_parameter_group_params の受け渡しは削除(custom_enable=false で未使用)
bash
cd fastdoctor-template/pf-mental-appointment-service/staging
export AWS_PROFILE=staging-terraform
./download-tfvar.sh
terraform init -input=false

# plan で `-pg16` が add(state 未登録・AWS 実在)になる場合は先に import
terraform import -var-file=terraform.tfvars \
  'module.pg16_blue_green_param_groups.aws_rds_cluster_parameter_group.target' mental-appointment-pg16
terraform import -var-file=terraform.tfvars \
  'module.pg16_blue_green_param_groups.aws_db_parameter_group.target'          mental-appointment-pg16

terraform plan -var-file=terraform.tfvars    # ★no-diff(downgrade / replace が出ないこと)
  • [ ] planno-diff(family13 内製 PG の destroy は 旧 blue 削除後にクリーンに成功)
  • [ ] apply は -target=module.microservice-ecs-tokyo.module.db 等でドリフト巻き込みを回避
  • [ ] ⚠️ Switchover 前にこの PR を apply しない(13.23 の実体に 16.14 を宣言する逆向きドリフトになる)
  • [ ] rds_instance_class が tfvars(db.t3.medium)と実体で一致していること(§7 で class を変えた場合は戻してから)

⑤ ★old1 削除前チェック(接続残留がないことの確認)

Switchover は endpoint 名を入れ替えるが、切替前の TCP 接続は old1 側に残り得る。残留があればその consumer は旧 PG13 を読み書きし続けるため、削除前に必ず確認する。old1 の cluster endpoint(mental-appointment-old1.cluster-…)へ踏み台から接続して見る。

sql
-- (1) old1 の素性(旧 PG13・別インスタンス・read-only 固定か)
SELECT current_setting('server_version') AS version, inet_server_addr() AS server_ip,
       pg_is_in_recovery() AS in_recovery, current_setting('default_transaction_read_only') AS ro;

-- (2) ★client 接続の列挙(アプリ IP が居ないこと)
SELECT coalesce(host(client_addr),'(local)') AS src, usename, state, count(*) AS conns,
       min(date_trunc('second', backend_start)) AS oldest_start,
       max(date_trunc('second', state_change))  AS last_activity
  FROM pg_stat_activity WHERE backend_type='client backend' GROUP BY 1,2,3 ORDER BY 1;

-- (3) データが Switchover 時点で凍結しているか(新側と突き合わせる)
SELECT (SELECT count(*) FROM appointment_patient_histories) AS hist_count,
       (SELECT max(recorded_at) FROM appointment_patient_histories) AS hist_max_recorded;
  • [ ] (1) server_version=13.23 / 新側と別の inet_server_addr() / ★default_transaction_read_only=on(RDS が old1 を read-only 固定=仮に残留しても書けない)
  • [ ] (2) ★アプリ IP(mental-appointment-service / online-ops-service)が 1本も無い(残るのは検証用 psql と rdsadmin の内部接続のみ)
  • [ ] (3) 新側の件数・最新値が old1 より進んでいる(=Switchover 後の write が old1 に来ていない=書き手が残っていない裏付け)

2026-08-19 実測:old1 は 13.23 / 10.10.19.222 / ro=on、client 接続は検証用 psql 1本+rdsadmin 5本のみ。件数は 新 8356(最新 13:39:31)↔ old1 8355(最新 08-14 07:16:03)。 補強:Switchover で接続 pid が入れ替わっていることも確認(Switchover 前に生存していた pid が消え、新側は Switchover 後に張られた接続のみ)=「接続は一瞬切断 → 自動再接続」どおり。

⑥ 後片付け(old1 → BG レコードの順)

⚠️ 保険期間の考え方:old1 を残す間は課金が続く。削除すると切り戻し手段が保険スナップショットのみになる。⚠️ old1 と snapshot はどちらも Switchover 時点のデータなので、切り戻せば Switchover 後の write は失われる(本番では「書込停止 or 差分許容の合意」が必須)。 BG レコードを消すと --delete-target 無しでの blue 継続オプションも失われるため、old1 とセットで削除する。

bash
# 1) 旧 blue(instance → cluster の順)
aws rds delete-db-instance --region $R --db-instance-identifier mental-appointment-0-old1 --skip-final-snapshot
aws rds wait db-instance-deleted --region $R --db-instance-identifier mental-appointment-0-old1
aws rds delete-db-cluster --region $R --db-cluster-identifier mental-appointment-old1 --skip-final-snapshot
aws rds wait db-cluster-deleted --region $R --db-cluster-identifier mental-appointment-old1

# 2) BG レコード
aws rds delete-blue-green-deployment --region $R --blue-green-deployment-identifier $BG

# 3) 確認
aws rds describe-db-clusters --region $R --query 'DBClusters[?starts_with(DBClusterIdentifier,`mental-appointment`)].DBClusterIdentifier'
aws rds describe-blue-green-deployments --region $R --query 'BlueGreenDeployments[].BlueGreenDeploymentName'
  • [ ] ⑤ のチェックが全て通ってから実施
  • [ ] 削除後 mental-appointment のみが残る/BG レコードが空
  • [ ] 保険スナップショット mental-appointment-pre-pg16-<YYYYMMDDHHMM>別途の保持方針に従う(BG 削除では消えない)
  • [ ] source CPG mental-appointment(family13)の削除・監視ダッシュボード撤去は別 PR

⑦ 監視

  • [ ] ダッシュボード mental-appointment-pg16-bluegreenDatabaseConnections / DBLoad / CPUUtilization / FreeableMemory が平常化
  • [ ] ⚠️ hash_mem_multiplier が PG16 で 1→2 になるためメモリ使用が増える。FreeableMemory / Temp file / slow query を移行後の監視対象にする(プレフライト doc 参照)
  • [ ] ⚠️ db.t3.medium はバースタブル。BG 初期コピーで CPUCreditBalance が大きく減る(実測 5.8 まで低下 → 回復)。移行直後は残クレジットを見る

7. (任意)Global DB 作成 → 削除の素振り(本番 Global 削除経路の検証)

本番 mental-appointment は Global DB のため、実移行では clone-rehearsal.md / production.md で remove-from-global-clusterdelete-global-cluster を通す。staging は非-Global だが、Global 作成→削除の CLI フローを staging 実クラスタで素振りして本番経路を検証できる。

⚠️ 実施順序(本番順序を再現するなら §7 を §4 の前に回す)

本書の §4 は「standalone クラスタに対する標準 BG」から始まる(staging が最初から非-Global だから)。一方**本番は「Global あり → 削除 → 標準 BG」という順序で、この接続部分(Global を外した直後のクラスタに BG を張る)**は §4 単独では素振りできない。

  • 本番順序まで再現したい場合: §7 ①②③(class 変更 → Global 作成 → detach+global 削除)を先に実施し、そのまま §4 の BG へ進む(§7 ④ の class 復帰は BG/Switchover 完了後に回す)。
    • この場合、BG は db.r7g.large 上で動くので所要時間・性能は本番寄りになる(コストも上がるため素振り時間は短くする)。
    • remove-from-global-cluster 直後は GlobalClusterIdentifier=null / Status=available / endpoint 不変を確認してから BG を作る(§4 F-1.5 の BG 作成前チェックに合流)。
  • 順序を再現しない場合(既定): §7 は §6 の後に独立した CLI 素振りとして実施してよい。Global 削除 → BG の接続部分は clone-rehearsal.md((2) Global 化 → (3) Global 削除 → (5) 標準 BG の順で本番同型)が主担当なので、staging 側で無理に再現しなくても経路は担保される。

⚠️ Aurora Global はバースタブル(t3系)非サポートstaging は db.t3.medium(実測)なので、Global 化の前に非バースタブル(例 db.r7g.large)へ一時変更が必須(変更・復帰とも再起動=数分の断)。素振り後に db.t3.medium へ戻す。利用の少ない時間帯に実施。

bash
export AWS_PROFILE=staging-admin; R=ap-northeast-1
CL=mental-appointment
# ① 【必須】t3.medium → 非バースタブルへ一時変更(Global は t3 非対応)
aws rds modify-db-instance --region $R --db-instance-identifier mental-appointment-0 --db-instance-class db.r7g.large --apply-immediately
aws rds wait db-instance-available --region $R --db-instance-identifier mental-appointment-0
# ② Global 作成
CL_ARN=$(aws rds describe-db-clusters --region $R --db-cluster-identifier $CL --query 'DBClusters[0].DBClusterArn' --output text)
aws rds create-global-cluster --region $R --global-cluster-identifier g-mental-appointment-stgtest --source-db-cluster-identifier "$CL_ARN"
# ③ ★本番と同じ削除経路: detach → 空 global 削除
aws rds remove-from-global-cluster --region $R --global-cluster-identifier g-mental-appointment-stgtest --db-cluster-identifier "$CL_ARN"
aws rds wait db-cluster-available --region $R --db-cluster-identifier $CL
aws rds delete-global-cluster --region $R --global-cluster-identifier g-mental-appointment-stgtest
# ④ 【必須】instance class を db.t3.medium へ戻す(コスト復帰)
aws rds modify-db-instance --region $R --db-instance-identifier mental-appointment-0 --db-instance-class db.t3.medium --apply-immediately
aws rds wait db-instance-available --region $R --db-instance-identifier mental-appointment-0
  • [ ] ②で Global 化・writer 1件・secondary 無し(本番と同型の単一リージョン)
  • [ ] ③で remove-from-global-cluster 後に GlobalClusterIdentifier=nullStatus=availableendpoint 不変
  • [ ] ③で delete-global-cluster が空 global を削除(メタ操作・無停止想定)
  • [ ] ④で db.t3.medium へ復帰(class 確認・コスト)