Skip to content

PG16 アップグレード サービス別パラメータ:eligibility-verification

手順本体は Cloneリハーサル版 / 本番実施版 を参照(本書は固有値と実施記録のみ)。 値の出典は上記2手順書および実機確認(2026-06)。

対象サービス: eligibility-verification担当: SRE(yusaku.ishizawa) 最終更新: 2026-06-19

1. 環境・接続

項目stagingproduction
AWS プロファイル(Pstaging-admin書き込み権限のある production プロファイル(⚠️ read-only 不可。#12905 参照)
リージョン(Rap-northeast-1ap-northeast-1
Secrets Manager Secret IDeligibility-verificationeligibility-verification
DB 名(psql dbnameeligibility_verificationeligibility_verification
踏み台(bastion・SSM ポートフォワード)fd-office 踏み台(describe-instances で確認)i-03b8c9b9fb3c4fe9a(fd-office-connection-bastion)(実機確認 2026-06-24)。SG sg-0c5ef6b0d1cf98489(fd-office-connection)が EVS DB SG sg-01a53660a7ef4826d inbound に含まれ到達可。⚠️ fd-platform 踏み台は EVS SG 不許可で不可
接続方式Prisma が DATABASE_URL 単一 Secret で接続(schema.prisma: url = env("DATABASE_URL"))。DB_HOST(ECS env)はアプリ未使用=変更不要。Secret 値・DB_HOST は Terraform 管理(jsonencode)のため手動変更は次の apply で上書きされる。同左

2. クラスタ構成(アップグレード対象)

項目
クラスタ識別子(SOURCE_CLeligibility-verification
インスタンス構成Writer 1台(eligibility-verification-0)/ Reader 無し(実機確認済み・本番でも describe-db-clusters の Members で再確認)
インスタンスクラスdescribe-db-instancesDBInstanceClass で取得(リハーサルでは source と同クラスに寄せる)
DB Subnet Groupeligibility-verification-subnet
VPC Security Groupdescribe-db-clustersVpcSecurityGroups[].VpcSecurityGroupId で取得
現行エンジン13.20
target エンジン16.13

Reader 無しのため、フェーズB の再起動は Writer 1台のみ。

3. パラメータグループ(#2164 で作成)

役割名前family
ソース側 custom CPG(PG13)eligibility-verificationaurora-postgresql13
ターゲット側 Cluster/Instance PG(PG16)eligibility-verification-pg16aurora-postgresql16
  • rds.logical_replication=1 / wal_sender_timeout=0(両方・pending-reboot
  • shared_preload_libraries: rdsutils,pg_stat_statements(source の rdsutils を落とさず pg_stat_statements を追加)。PG16 ターゲット CPG にも同値を揃える。
  • ソース側 custom CPG は microservice-ecscluster_parameter_group_custom_enable=true で作成。ターゲット側は template_modules/options/aurora-bluegreen-param-groups
  • ⚠️ 本番は CPG 未作成。staging の #2164 と同じ差分を production に展開する apply が必要(本番手順 フェーズA-1)。

4. 使用拡張・後処理の該当有無(実機確認・2026-06)

項目該当備考
インストール拡張(\dxplpgsql のみpg_stat_statements は別途 CREATE EXTENSION
pg_stat_statementspreload 済み・CREATE EXTENSION → Switchover 後に ALTER EXTENSION ... UPDATE(1.10 へ)
pg_cron / pg_partman(bgw)未使用該当なし
pg_repack未使用該当なし
Auto Scaling / ReaderなしWriter 1台

→ 後処理で実際にやるのは ①拡張更新(pg_stat_statements)②(任意)pg_stat_statements リセット ③アプリエラーログ監視 の3つのみ。

5. Blue/Green 名・スナップショット名

項目
Blue/Green 名(BG_NAME、本番)eligibility-verification-bg
Blue/Green 名(リハーサル)eligibility-verification-bgtest
Clone 名(リハーサル CLeligibility-verification-bgtest-blue
Switchover 前スナップショットeligibility-verification-pre-pg16-<YYYYMMDDHHMM>

6. 整合確認テーブル(Green/Switchover 後の件数確認)

sql
SELECT COUNT(*) FROM _prisma_migrations;
SELECT COUNT(*) FROM ocr_results;
SELECT COUNT(*) FROM prompt_definitions;

ライブ複製の非侵襲追従確認(staging-direct ②):

  • high_write_table = ocr_results、確認式 = SELECT max(id) FROM ocr_results;(id は単調増加・INSERT 専用ログのため max(id) が適切。OCR を1件流すと +1)。Blue / Green で同値に追従=複製 OK。
  • アプリ経由テストデータの後始末: OCR で入った ocr_results 行はアプリが読み返さず無害。削除する場合は staging 限定で DELETE FROM ocr_results WHERE id=<id>;(id を控えておく)。

6b. アプリ経由のライブ検証(staging 実DB 直接適用版で実施)

procedure-staging-direct.md の「アプリ経由のライブ検証」を eligibility-verification の staging 実DB で行うための固有情報。

アプリの DB 書込(コードで確定・mental-online-karte #13098 コメント 2026-06-10)

  • EVS api で Aurora(Prisma) を触るのは OCR モジュールだけ。資格確認(/v1/insurance-card)・電子処方箋は Aurora を触らない(外部APIのみ)。
  • テーブルは2つ: prompt_definitions(OCR指示文・READ/管理時のみ WRITE)/ ocr_results(OCR結果・WRITE 専用ログ。アプリは読み返さない)
  • 書込トリガー: GraphQL mutation previewInsuranceCardOcr(または previewMedicalCertificateOcr)。1リクエスト=prompt_definitions READ + ocr_results1行 INSERT(jsonb)
  • 認証/実行: fdc api graphqlpnpm fdc auth loginis:Operator。トークンは自動リフレッシュ)。

アプリ経由の書込を1件起こす最小手順(staging 限定・実PII不可)

詳細・実証ログは #13098 コメント(2026-06-10)。要点のみ:

  1. ダミー保険証画像(「TEST/DUMMY」明記)を生成 → S3 s3://eligibility-verification-staging/evs-ocr-test/ に put → aws s3 presign(1h)。
  2. 署名付きURL を &/= でシェルが壊さないようファイル経由で GraphQL に埋め込み:
    bash
    # /tmp/evs_presigned_url.txt に presign URL を保存後
    python3 - <<'PY'
    url=open('/tmp/evs_presigned_url.txt').read().strip()
    open('/tmp/evs_ocr_query.gql','w').write(
      'mutation { previewInsuranceCardOcr(input:{ imageUrl:"%s" }) '
      '{ result { ocrResult { insuranceNumber } alert } userErrors { message code } } }'%url)
    PY
    cat /tmp/evs_ocr_query.gql | pnpm --silent fdc api graphql   # userErrors:[] = 成功(ocr_results に1行 INSERT)
  3. 後始末: S3 のダミー画像と一時ファイル削除(テスト行は無害=アプリは読み返さない。消すなら staging で DELETE FROM ocr_results WHERE id=<id>)。

検証手順(Blue/Green の各フェーズで上記 OCR を実行)

  1. フェーズC(同期中・Blue 無停止): アプリで OCR 1件実行 → Blue の ocr_results 最新 id を控える → Green(read-only/別トンネル)で同 id が出現=INSERT が論理レプリケーションで複製されることを確認。
    sql
    SELECT id, created_at FROM ocr_results ORDER BY id DESC LIMIT 1;   -- Blue / Green 双方
    (ラグで Green 反映が一瞬遅れる点に留意。OCR は INSERT(DML) なので複製される=壊すのは DDL。)
  2. フェーズD(Switchover 前後): 切替直前に OCR 1件(ベースライン)→ 直後に OCR 1件 → ocr_results が +1 されれば据え置きエンドポイントで新 PG16 にアプリ経由 read/write OK。書込断(数秒)中のアプリ挙動も観察。
  3. フェーズE: アプリのエラーログ監視+ OCR フロー疎通確認。

⚠️ staging 限定(本番 acct 967691968827 / .fstdr.jp では実施しない)。実PII不可。Textract+Bedrock の少額課金+ocr_results 1行が発生するので後始末する。ocr_results は本番 OCR(個人情報含み得る)も入るため、調査時は SELECT * を避け id/件数/時刻のみ参照。

画面(fastdoctor-manager)からの接続確認 — 実証手順(2026-06-22 実施)

fdc api graphql を使わず、fastdoctor-manager の管理画面操作だけで EVS Aurora への接続・書込を確認する手順(実機検証済み)。

前提:どの画面操作が EVS Aurora を触るか(重要)

画面操作経路EVS Aurora
「オン資確認実行」ボタン/admin/kartes/:id/eligibility_verificationEVS の /v1/insurance-card を HTTP → 外部 OQS 照会のみ。結果は fastdoctor-manager 自身の DBeligibility_verifications/kartes/patients)に保存触らない(接続も書込も発生しない)
保険証/医療証の画像アップロード(OCR)Image 保存 → ExecuteOcrJob(非同期)→ OcrPreviewService → EVS v1/ocr/previewocr_results に +1(接続も発生)★これで確認する

EVS Aurora の接続確認は「保険証画像の OCR」で行う(「オン資確認実行」では EVS Aurora は動かない)。Flipper ocr_from_eligibility_verification が有効なこと(OFF だと legacy Ocr::Service 経由で EVS を通らない)。

手順

  1. ダミー保険証画像を用意(実PII不可・「TEST/DUMMY・テスト タロウ」等を明記)。例: Pillow + ヒラギノで生成(健康保険証 のレイアウトに寄せると OCR 分類が通りやすい)。
  2. EVS Aurora を read-only 監視(接続: fd-office 踏み台 i-0cefcd4a25e8703e5 経由 SSM。fd-platform 踏み台は EVS SG 不許可):
    sql
    -- ベースライン
    SELECT count(*) AS cnt, max(id) AS max_id, max(created_at) AS latest FROM ocr_results;
    -- 接続状況(ECS から active になるか)
    SELECT pid, client_addr, state, state_change FROM pg_stat_activity
      WHERE datname='eligibility_verification' AND usename='aK6Fr2fA' AND pid<>pg_backend_pid();
  3. 画面で保険証画像をアップロード。実際の画面遷移(staging・2026-06 実施):
    1. オンライン診療 依頼フォーム https://contact-test.fastdoctor.jp/online-consultation/?scene=complete(Basic 認証 fast/fast1)で患者情報を登録して案件を作成。
    2. オンライン医師差配 https://backend-stg.fstdr.jp/online_coordinator/ で、作成した患者の詳細情報モーダルを開く
    3. モーダルの 「保険証[未登録]」をクリックhttps://backend-stg.fstdr.jp/admin/medical_examinations/<id>/receipt の receipt 画面へ。
    4. その画面で保険証の写真をアップロード(=OCR トリガー)。
    • 非同期ジョブExecuteOcrJob)のため、ocr_results への反映は数十秒〜数分遅れることがある。
  4. EVS Aurora を再確認ocr_resultsmax_id/件数が +1、新規 ECS 接続が active→idle になれば「画面操作→EVS Aurora 書込」が成立。
    sql
    SELECT id, created_at,
           (result::text ILIKE '%TEST%' OR raw_result::text ILIKE '%TARO%') AS dummy,  -- ダミー判定(PII 非表示)
           (coalesce(alert,'')='') AS classified_ok                                     -- alert 空=健康保険証として分類成功
    FROM ocr_results WHERE id > <baseline max_id> ORDER BY id;

実測例(2026-06-22 staging): 画像アップロード → ocr_results4293→4294→4295 と +1 ずつ増加、dummy=t / classified_ok=t(健康保険証として分類成功・alert なし)。「オン資確認実行」では同 DB は 無変化(接続も idle のまま)であることも確認=経路の違いを実証。

注意・落とし穴

  • 非同期遅延: ExecuteOcrJob はジョブのため即時反映でないことがある(数分待って再確認)。
  • 冪等スキップ: ExecuteOcrJobreturn if image.ocr_result.present?同じ画像の再アップロードは EVS を呼ばない(増えないのが正常)。確認は毎回別の新規画像で。
  • 分類失敗: OCR が「健康保険証/医療証」と分類できないと alert に「…どちらでもありませんでした」が入る(行は作られるが processed_result={})。ダミーは保険証レイアウトに寄せる。
  • staging 限定・実PII不可・テスト行は後始末(DELETE FROM ocr_results WHERE id IN (...))。

7. Terraform 整合対象(Switchover 後)

  • パス: fastdoctor-template/eligibility-verification/production/(staging は .../staging/、PG16 CPG は pg16_param_groups.tf
  • rds_engine_version"13.20""16.13"
  • parameter group family を aurora-postgresql16 に、eligibility-verification-pg16 参照へ
  • terraform plan に downgrade / replace(ForceNew)が出ないことを確認してから apply

8. 関連 Issue / PR

  • 親チケット: mental-online-karte #12725 「[DB] eligibility-verification PostgreSQL 16系アップグレード」
  • 本番手順書 Issue: mental-online-karte #13721
  • staging 調査・検証の進捗: mental-online-karte #13098
  • Clone リハーサル Issue: mental-online-karte #13719
  • 実装メモ(カスタム PG / logical_replication / PG16 ターゲット): mental-online-karte #12905
  • 対象 DB の棚卸し・方針決定: mental-online-karte #13044
  • PG16 用 custom parameter group 追加(staging・MERGED): terraform_for_aws #2164

9. 実施記録

日付環境フェーズ結果・所要・気付き
2026-06-16〜17stagingCloneリハーサルGreen 作成 約33分(Read Replica 約16分+PG16 化 約14分)。Green offline 約12分(Blue 無影響)。Switchover 書込断 約2秒。
2026-06-25〜26stagingstaging-direct(実DB直接)B→C→D 完走(#14063)。Switchover 書込断 約2秒(events 実測)。Green=16.13 / Blue==Green 整合 / ライブ複製OK / 拡張 1.10 / slot 0 / A-0事後 0。アプリ(画面・GraphQL 両経路)→新PG16 書込OK。
未実施production本番メンテ枠2つ(①CPG付替+再起動 ②Switchover)を別枠で確保予定。snapshot 名・結果は実施時に追記。

10. 本番実行コマンド(#14063 staging 実績ベース・production 値・AWS CloudShell 前提)

staging-direct(mental-online-karte #14063)で実行した一連のコマンドを production 値に置換し、AWS CloudShell から実行する前提でまとめたもの。手順本体の解説は procedure-production.md を正とし、本節は「そのまま流せる具体コマンド」。 ⚠️ 本番は フェーズB(メンテ枠①)と フェーズD(メンテ枠②)を別枠で実施(必須)。周知は #on本部_release-ops#fdtech-general

10-0. CloudShell 前提・共通変数

  • CloudShell を production アカウント(967691968827)のコンソールで開く。コマンドに --profile は付けない(CloudShell はログイン中コンソール ID の認証情報を使う)。--region は明示的に付ける(既定リージョンに依存しない)。
  • ⚠️ 実行ロールに書き込み権限が必要: rds:ModifyDBCluster / RebootDBInstance / CreateDBClusterSnapshot / SwitchoverBlueGreenDeploymentsecretsmanager:GetSecretValuessm:StartSessionread-only ロールでは不可
  • psql を別途インストール: sudo dnf install -y postgresql16(CloudShell は Amazon Linux)。
  • ポートフォワードと psql は CloudShell の別タブ(Actions → New tab。同一環境で localhost 共有)。\copy は1行で書く(折り返し不可)、CSV は $HOME 配下に出力して「Actions → Download file」で取得。
  • 後始末(毎回・必須): psql は \q で抜けたら unset CREDS PGUSER PGPASSWORD(資格情報を環境変数に残さない)。port-forward タブは Ctrl-C で終了。ダウンロードした CSV 等の機微ファイルは作業後に削除し、共有範囲を限定する。
  • ⚠️ read-only 確認用と「DDL/ANALYZE 実行用」のセッションを分ける:
    • 参照確認だけのセッション → SET default_transaction_read_only = on; で固定(事故防止)。
    • CREATE EXTENSION(B-5)/ ALTER EXTENSION ... UPDATE(D-5)/ ANALYZE(C-3)を行うセッションでは default_transaction_read_only = on を設定しない。read-only のままだと cannot execute ... in a read-only transaction で失敗する。安全確認の癖で read-only を入れたまま DDL すると失敗するので注意。
bash
# 認証確認(Account=967691968827・書き込みロールであること)
aws sts get-caller-identity --query '{Account:Account,Arn:Arn}' --output json

export R=ap-northeast-1
export SOURCE_CL=eligibility-verification
export BG_NAME=eligibility-verification-bg
export BASTION=i-03b8c9b9fb3c4fe9a                       # fd-office-connection-bastion(production)
export DB_HOST=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].Endpoint" --output text --region $R)   # ≒ eligibility-verification.cluster-cvwvyuc2cloj.ap-northeast-1.rds.amazonaws.com

前提: A-1(Terraform で本番に CPG 作成)= terraform_for_aws #2253 を apply 済みであること(eligibility-verification / eligibility-verification-pg16 が存在)。

10-T. タイムスケジュール例(準備 4時間 + 正式メンテ枠 1時間)

「A-3 ベースライン取得 〜 D-2 直前」までの準備フェーズを約4時間(Blue は無停止/書込断は Switchover の数秒のみ=サービス停止ではない)、D-2 Switchover 以降を正式なダウンタイム(サービス停止)1時間枠として設計する例。 ⚠️ フェーズB(CPG 付替+再起動=瞬断)は別の事前メンテ枠で実施済みであること(この 4h/1h とは別枠)。A-3 ベースラインは B-5 で拡張作成後、事前に通常トラフィックを蓄積済みである前提。

準備フェーズ(約4時間・Blue 無停止・枠外でも可)

経過作業備考
T-4:00A-3 ベースライン取得(Top SQL+CSV・Blue/PG13)read-only。トラフィック蓄積済み前提
T-3:55C-1 BG 可否ゲート確認(available / Global 非所属 / 既存BGなし / 16.13可)
T-3:50C-2 BG 作成 → AVAILABLE 待ち(実測 約31〜33分)Green のみ offline・Blue 無停止
T-3:10C-3 Green 検証(server_version=16.13 / 件数整合 / ANALYZE)+ ライブ複製確認
T-1:00C-4 DDL/migration 凍結の最終確認同期中は Blue へ DDL を流さない
T-0:30D-0 最終ゲート(AVAILABLE / Details=null / SwitchoverDetails 各 AVAILABLE)+ レプリラグ0 + 長時間tx0
T-0:20D-1 手動スナップショット取得 → available(数分・ロールバック保険)
T-0:05D-1.5 最終ゲート再確認・GO/NO-GO 判断NG なら枠を開かず延期

正式メンテ枠(T0〜T+1:00・サービス停止扱い・周知済み)

経過作業備考
T0D-2 Switchover 実行(書込断 実測 約2秒
T+0:02D-3 完了・リネーム確認(SWITCHOVER_COMPLETED / Renamed)
T+0:05D-4 PG16 疎通・アプリ再接続・主要機能(OCR write)確認port-forward 張り直し(原名→新PG16)
T+0:15D-5 拡張更新(ALTER EXTENSION UPDATE)/ slot 確認 / A-0事後(-old1 残留 0)
T+0:20〜T+1:00監視バッファ(40分): エラー率・レイテンシ・DB接続・主要機能を監視。ロールバック判断者待機異常時は #12725 §13 基準で判断(復元元 $SNAP
  • 実際の書込断は Switchover の数秒だが、再接続・DNS 伝播・事後確認・ロールバック余地を含め1時間を「サービス停止」枠として周知する(安全マージン)。
  • 準備4時間の大半は BG 作成(約33分)+検証+snapshot+ゲートで、Blue は稼働継続=この間はサービス停止ではない。

10-B. フェーズB(CPG 付替 → 再起動=瞬断・メンテ枠①)

bash
# B-0 付け替え前の確認(4点)
# ① 付け替える CPG の中身が期待値か(rds.logical_replication=1 / wal_sender_timeout=0 /
#    shared_preload_libraries に pg_stat_statements を含む。⚠️ shared_preload_libraries は上書き型=
#    本番の現値 rdsutils,pg_stat_statements の既存 preload を落としていないこと。instance では rdsutils が自動付与される)
aws rds describe-db-cluster-parameters --db-cluster-parameter-group-name eligibility-verification \
  --query "Parameters[?ParameterName=='rds.logical_replication'||ParameterName=='wal_sender_timeout'||ParameterName=='shared_preload_libraries'].[ParameterName,ParameterValue,ApplyMethod]" --output table --region $R
# ② 現在クラスタに紐づく CPG・状態を控える(Status=available / Engine=13.20 / 全 Members PGStatus=in-sync)
#    ⚠️ 付け替え前から pending-reboot が残っていたら別の未反映変更が混在の疑い → 一旦中止して調査
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].{Cluster:DBClusterIdentifier,Engine:EngineVersion,Status:Status,CurrentCPG:DBClusterParameterGroup,Members:DBClusterMembers[].{Instance:DBInstanceIdentifier,Writer:IsClusterWriter,PGStatus:DBClusterParameterGroupStatus}}" --output json --region $R
# ③ Reader 有無=再起動対象の確認(eligibility は Writer 1台。Reader ありなら B-3 は Reader→Writer の順)
# ④ 外部CDC(Datastream 等)が無いことの確認(付け替え=logical on の前に「クリーンな初期状態」を確認)
# ④-a AWS: DB SG inbound 5432 のソース(アプリ+踏み台のみが正・外部ツール用の許可が無いこと)
DB_SG=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].VpcSecurityGroups[0].VpcSecurityGroupId" --output text --region $R)
aws ec2 describe-security-groups --group-ids "$DB_SG" --region $R \
  --query "SecurityGroups[0].IpPermissions[?FromPort==\`5432\`].[UserIdGroupPairs[].GroupId,IpRanges[].CidrIp]" --output json
# ④-b DB内部: port-forward(B-5 の手順で localPort 5432)+ Secret 取得後、read-only で確認
#     ※logical off の今は slot は構造的に 0。publication は定義として残り得るので要確認
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require" \
  -c "SET default_transaction_read_only=on;" \
  -c "SELECT slot_name, plugin, slot_type, active FROM pg_replication_slots;" \
  -c "SELECT pubname, puballtables FROM pg_publication;" \
  -c "SELECT application_name, state FROM pg_stat_replication;"
#   期待: いずれも 0行。⚠️ 外部 logical slot/publication があると後続の BG 作成が "external replication" で失敗(最終ゲートは C-1 で再確認)
#   ※この psql -c は独立した接続で、`SET default_transaction_read_only=on` も接続終了で消滅する。
#     よって B-5 の CREATE EXTENSION(別セッション・read-only にしない)には波及しない(接続ごとに独立)。
#     同一対話セッションで続けて DDL する場合のみ、事前に `SET default_transaction_read_only=off;` に戻すこと。

# B-1 付け替え(無停止・反映は再起動時)
aws rds modify-db-cluster --db-cluster-identifier $SOURCE_CL \
  --db-cluster-parameter-group-name eligibility-verification --apply-immediately --region $R
# B-2 pending-reboot 確認
aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query 'DBClusters[0].DBClusterMembers[].{Instance:DBInstanceIdentifier,Writer:IsClusterWriter,PGStatus:DBClusterParameterGroupStatus}' --output table --region $R
# B-3 再起動(Reader→Writer。eligibility は Writer 1台)=瞬断
READER_IDS=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].DBClusterMembers[?IsClusterWriter==\`false\`].DBInstanceIdentifier[]" --output text --region $R)
for RID in $READER_IDS; do aws rds reboot-db-instance --db-instance-identifier "$RID" --region $R; aws rds wait db-instance-available --db-instance-identifier "$RID" --region $R; done
WRITER_ID=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL \
  --query "DBClusters[0].DBClusterMembers[?IsClusterWriter==\`true\`].DBInstanceIdentifier | [0]" --output text --region $R)
aws rds reboot-db-instance --db-instance-identifier "$WRITER_ID" --region $R
aws rds wait db-instance-available --db-instance-identifier "$WRITER_ID" --region $R
# B-4 in-sync 確認(上の describe を再実行)

B-5 接続 → logical 確認 → 拡張作成(タブ1 で port-forward、タブ2 で psql):

bash
# タブ1(開けたまま)
aws ssm start-session --target $BASTION --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters "{\"host\":[\"$DB_HOST\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"5432\"]}" --region $R
# タブ2
CREDS=$(aws secretsmanager get-secret-value --secret-id eligibility-verification --query SecretString --output text --region $R)
export PGUSER=$(echo "$CREDS" | python3 -c "import sys,json;print(json.load(sys.stdin)['DB_USERNAME'])")
export PGPASSWORD=$(echo "$CREDS" | python3 -c "import sys,json;print(json.load(sys.stdin)['DB_PASSWORD'])")
unset CREDS
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require"
# ↓ psql を抜けたら(\q)必ず資格情報を環境から消す(毎回・必須)
unset CREDS PGUSER PGPASSWORD
# port-forward(タブ1)は作業終了後 Ctrl-C で閉じる
sql
SHOW rds.logical_replication;  -- on
SHOW wal_level;                -- logical
SHOW wal_sender_timeout;       -- 0
-- ※このセッションは read-only に固定しない(DDL のため)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;   -- BG 作成前に(writable なうち)
\dx pg_stat_statements

on/logical/0 が揃わなければ フェーズC へ進まない。

A-3 ベースライン(Top SQL 抽出+CSV化): CREATE EXTENSION 後、通常トラフィックを一定期間蓄積してから取得(作成直後は統計が空=意味なし)。PG13 のカラムは total_exec_time/mean_exec_time

画面表示(psql 内):

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 化(CloudShell ではシェルから psql -c を正とする$HOME が確実に展開され、\copy の折り返しも回避できる。port-forward 接続中・別タブで1行):

bash
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require" \
  -c "\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 '$HOME/pg_stat_baseline.csv' CSV HEADER"

⚠️ psql 対話内で \copy する場合は $HOME が展開されないため、絶対パスを明示する(例 /home/cloudshell-user/pg_stat_baseline.csv)。確実なのは上記のシェル psql -c。 ⚠️ CSV の取り扱い注意: pg_stat_statements.query は通常は正規化 SQL だが、クエリによっては業務上見せたくない情報を含み得る。保管先・共有範囲を限定し、不要になったら削除する(CloudShell のファイルも作業後に削除)。 取得した CSV は フェーズE(事後)の Top SQL と突き合わせて性能退行を判断(queryid はアップグレードで変わるため、クエリ本文・呼出回数・mean/total 時間で比較)。

10-C. フェーズC(Blue/Green 作成・Blue 無停止)

bash
# C-1 可否ゲート(available / Global 非所属 / 既存BGなし / 16.13可)→ procedure-production A-2 参照
#    ★外部CDC 最終確認(port-forward=localPort 5432 + Secret 取得後・read-only。BG 作成直前)
psql "host=localhost port=5432 dbname=eligibility_verification sslmode=require" \
  -c "SET default_transaction_read_only=on;" \
  -c "SELECT slot_name, plugin, slot_type, active FROM pg_replication_slots;" \
  -c "SELECT pubname, puballtables FROM pg_publication;" \
  -c "SELECT application_name, state FROM pg_stat_replication;"
#    期待: BG 用以外は 0 行(外部 logical slot/publication があると create-blue-green-deployment が "external replication" で失敗)
# C-2 BG 作成
SRC_ARN=$(aws rds describe-db-clusters --db-cluster-identifier $SOURCE_CL --query 'DBClusters[0].DBClusterArn' --output text --region $R)
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
export BG=<返ってきた bgd-xxxx>
# AVAILABLE 待ち(実測 約31〜33分。Green のみ offline・Blue 無停止)
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
  --query "BlueGreenDeployments[0].{Status:Status,Details:StatusDetails,Tasks:Tasks[].{n:Name,s:Status}}" --output json --region $R

C-3 Green 検証(Green エンドポイントへ別タブ・別ポート 15432 で port-forward → psql):

bash
GREEN_ARN=$(aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG --query "BlueGreenDeployments[0].Target" --output text --region $R)
GREEN_DB_HOST=$(aws rds describe-db-clusters --db-cluster-identifier "$GREEN_ARN" --query "DBClusters[0].Endpoint" --output text --region $R)
aws ssm start-session --target $BASTION --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters "{\"host\":[\"$GREEN_DB_HOST\"],\"portNumber\":[\"5432\"],\"localPortNumber\":[\"15432\"]}" --region $R
sql
SHOW server_version;            -- 16.13
SHOW shared_preload_libraries;  -- rds_blue_green / writeforward を含む=Green の証拠
SELECT COUNT(*) FROM ocr_results;   -- Blue と一致
ANALYZE VERBOSE;                -- 切替直後の性能事故防止(※read-only 固定しないセッションで。ANALYZE は統計更新=read-only 不可)

ライブ複製確認: アプリで OCR 1件 → Blue の max(id) を控える → Green で同 id 出現で複製OK。C-4: 同期中は Blue へ DDL/migration を流さない。

10-D. フェーズD(Switchover・メンテ枠②)

bash
# D-0 最終ゲート(AVAILABLE / Details=null / SwitchoverDetails 各 AVAILABLE)
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
  --query "BlueGreenDeployments[0].{Status:Status,Details:StatusDetails,Sw:SwitchoverDetails[].Status}" --output json --region $R
# レプリラグ=0(Blue writer の slot): SQL でも確認可
#   SELECT slot_name,active,pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),confirmed_flush_lsn)) FROM pg_replication_slots;  -- 0 bytes
# 長時間tx=0: Blue(5432) で pg_stat_activity 確認

# D-1 手動スナップショット(Blue=PG13・ロールバック保険)→ available 待ち
SNAP=eligibility-verification-pre-pg16-$(date +%Y%m%d%H%M)
aws rds create-db-cluster-snapshot --db-cluster-identifier $SOURCE_CL --db-cluster-snapshot-identifier "$SNAP" --region $R
aws rds wait db-cluster-snapshot-available --db-cluster-snapshot-identifier "$SNAP" --region $R

# D-1.5 snapshot available 後・Switchover 直前に最終ゲートを再確認(snapshot 待ちで数分経つため)
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG \
  --query "BlueGreenDeployments[0].{Status:Status,Details:StatusDetails,Sw:SwitchoverDetails[].Status}" --output json --region $R
#   期待: Status=AVAILABLE / Details=null / SwitchoverDetails 各 AVAILABLE(崩れていたら Switchover しない)

# D-2 Switchover(書込断 数秒)
aws rds switchover-blue-green-deployment --blue-green-deployment-identifier $BG --switchover-timeout 300 --region $R
# D-3 完了・リネーム確認
aws rds describe-blue-green-deployments --blue-green-deployment-identifier $BG --query "BlueGreenDeployments[0].Status" --output text --region $R  # SWITCHOVER_COMPLETED
aws rds describe-events --source-type db-cluster --source-identifier $SOURCE_CL --duration 30 --query "Events[].[Date,Message]" --output table --region $R

D-4 PG16 疎通(Switchover 後はエンドポイント名が新PG16を指す。port-forward を張り直して 5432 で接続)→ SHOW server_version;=16.13 / SELECT count(*) FROM ocr_results; 整合 / アプリ再接続・OCR 書込確認。 D-5 拡張更新・slot 確認(新PG16・writable):

sql
-- ※このセッションは read-only に固定しない(DDL のため)
ALTER EXTENSION pg_stat_statements UPDATE;   -- 1.10
SELECT name, installed_version, default_version FROM pg_available_extensions WHERE installed_version IS NOT NULL AND installed_version <> default_version;  -- 0行
SELECT slot_name, slot_type, active FROM pg_replication_slots;  -- eligibility は 0行が正

A-0 事後(旧 Blue -old1 の残留接続。別タブ・別ポート 15433 で接続):

bash
OLD_HOST=$(aws rds describe-db-clusters --db-cluster-identifier ${SOURCE_CL}-old1 --query "DBClusters[0].Endpoint" --output text --region $R)
# port-forward(15433) → psql で:
#   SELECT usename,application_name,client_addr,count(*) FROM pg_stat_activity WHERE pid<>pg_backend_pid() AND client_addr IS NOT NULL GROUP BY 1,2,3;  -- 0行が理想

10-E. フェーズE(事後)

  • 性能監視(実トラフィック最低3日・A-3 ベースライン比較)。
  • Terraform 整合production/main.tfrds_engine_version=16.13 / rds_family=aurora-postgresql16)。staging の実績 PR: terraform_for_aws #2263。apply は -target=module.microservice-ecs.module.db 等でドリフト巻き込みを回避。
  • eligibility-verification-old1(PG13)は数日安定確認後に削除。
  • 後始末 BG: delete-blue-green-deployment(Switchover 後は --delete-target を付けない)。

⚠️ 本番固有の注意: メンテ枠①②を別枠(必須)/周知 #on本部_release-ops#fdtech-general(3〜5営業日前+前日+当日)/監視 mute/DNS TTL≤5s/ロールバック元 $SNAPlogical_replication を移行後に off へ戻す等の値見直しは #13044。